--[[****************************************************************
	DuckieBank v2.41

	Author: Evil Duck
	****************************************************************
	Description:
		Type /db or /duckiebank for brief help

	Credits:
		Thanks to wow.jaslaughter.com for the great BankItems addon
		that really helped this project get started.
		Additional thanks to:
			Yatlas and Gello
			wowwiki.com
			lua-users.org

		My guild, The Dawn of Alliance (Aszune), for great patience
		throughout the testing-period.

	Official Site:
		http://www.duckmod.com/
		(still working on it, tho)

	****************************************************************]]

-- 2.41a Changed a timer for bank-snapshot
-- 2.41  Fixed rare bug that could happen after server patch and/or restart, workaround-test for when data can't be sent, added
--       timestamp to items, added sorting by level/type/subtype, better handling when switching guilds, added spanish locale
-- 2.40b Another LUA compatibility fixed after 2.1 patch, DuckieBank is smoother when adding/removing/moving in bank/bags
-- 2.40a Fixed incompatibility with WoW 2.1.0, bugfix in banker assignment through officer's note, fixed bug in guild roster display
-- 2.40  Strengthened test for uncached items, new request-window, added options-window, added banker-setting in officer's note,
--       changed WoW API call for rank-test (hope that works better with the servers out there), changed scan-scheme to better
--       work with bag-modifier addons, more agressive removal of non-bankers, faster tooltips for items (up to a point of max client-items),
--       separated realms, improved sorting and colour-coding of items, basic filtering implemented, mousewheel handler.
-- 2.36  Fixed bug in item-stacking (and consequently also request-system), added money, fixed reloadui issue
-- 2.34a Added a cleaning-function of incorrect bankers
-- 2.34  Added colour-coded frames, sorting of items, single-stacks, mouse-over request-list, bag selector, unstacked prices, server verbose
-- 2.32  A couple compatibility issues fixed with the new LUA engine in WoW 2.0
-- 2.31  Fixed bug in request clean-up
-- 2.30  Request-system, bugfix in banker removal, redesigned poll-system, other minor bugfixes
-- 2.20  Revamped UI, added cost-system, changed poll-timer scheme, note-bug fixed
-- 2.11  Fixed bug in INFO receive
-- 2.10  Added notes for bankers
-- 2.08  Bugfix for bankers who are not including their bags
-- 2.07  Minor fixes for banker storage
-- 2.06  Includes version when polling, picks up unknown bankers when seen
-- 2.05  Added send-button, moved banker date, 3 minutes holdoff timer, bankwindow can stay closed for bag update
-- 2.04  Some debug, and added checking of version
-- 2.03  Removed "normal" channels
-- 1.99  Extracted the network code to form the DuckNet addon
-- 1.51  New layout and multiple bankers test-version
-- 1.02  In-game protocol really coming along. First guild-wide test started.
-- 1.01  First move for in-game protocol
-- 0.10  Clean-up for first release
-- 0.02  Added minimap button
-- 0.01  Added display of saved date and time
-- 0.00  Most stuff is there


DUCKIEBANK_VERSIONTEXT = "DuckieBank v2.41a";
DUCKIEBANK_REQUIRED_DUCKNET = 1;
SLASH_DUCKIEBANK1 = "/duckiebank";
SLASH_DUCKIEBANK2 = "/db";

DUCKIEBANK_VERSION_DATA = "002";	-- The data structure in use
DUCKIEBANK_VERSION_BANKNUM = "0241";	-- The DuckieBank numeric string
DUCKIEBANK_PREFIX = "DuBa";
DUCKIEBANK_MARKER0 = "DuckieBank-Marker0";
DUCKIEBANK_MARKER1 = "DuckieBank-Marker1";
DUCKIEBANK_MARKER2 = "DuckieBank-Marker2";
DUCKIEBANK_MARKER3 = "DuckieBank-Marker3";
DUCKIEBANK_MARKER4 = "DuckieBank-Marker4";
DUCKIEBANK_MARKER5 = "DuckieBank-Marker5";
DUCKIEBANK_MARKER6 = "DuckieBank-Marker6";
DUCKIEBANK_MARKER7 = "DuckieBank-Marker7";
DUCKIEBANK_MARKER8 = "DuckieBank-Marker8";
DUCKIEBANK_MARKER9 = "DuckieBank-Marker9";
DUCKIEBANK_COLLATE = "DuckieBank-Collate";
DUCKIEBANK_REQUEST = "DuckieBank-Requests";
DUCKIEBANK_SETTING = DUCKIEBANK_MARKER0;

ITEMCOLORGREEN="m060"
ITEMCOLORYELLOW="m120"
ITEMCOLORORANGE="m150"
ITEMCOLORCYAN="p000"
ITEMCOLORBLUE="p060"
ITEMCOLORPURPLE="p090"
ITEMCOLORPINK="p120"
ITEMCOLORRED="p180"

DuckieBank_DB = { };

--[[	DB structure

guild 1
	1
		Banker 1
		Stamp
		Notes
		Settings
			Stamp = Stamp
			<Setting> = "Setting data"
			<Setting> = "Setting data"
	n
		Banker n
		Stamp
		Notes
		Settings
			...
	Banker 1
		1 { slot 1 }
		n { slot n }
	Banker n
		1 { slot 1 }
		n { slot n }
	Requests
		Banker 1
			Player 1
				Stamp
				1 { itemID, Count, Notes, Stamp [, Removed] }
				n { itemID, Count, Notes, Stamp [, Removed] }
			Player 2
				...
		Banker n
			...
				...
guild n
	...
]]

DuckieBank_LS = {
	IconPosition = 180,
	Debug=false,
	DebugServer=false,
	LastIncomingRequest = {},
	NotAgressive = nil,
};
local DuckieBank_LastGuild = nil;
local DuckieBank_LastGuildName = nil;
local DuckieBank_Banker = false;
local DuckieBank_Monitor = false;
local DuckieBank_VersionNotified = false;		-- User has (or has not) been notified about a new version if that exists
local DuckieBank_OldBankerNotes="";
local DuckieBank_DataLoaded=false;
local DuckieBank_DataCache=nil;
local DuckieBank_WrongDuckNet=nil;
local DuckieBank_CacheRetries=0;
local DuckieBank_SendDelayed=0;

local DuckieBank_Ready = false;
local DuckieBank_Transmitting = 0;
local DuckieBank_Trans = { };
local DuckieBank_BankerSend=nil;
local DuckieBank_EditItem=nil;
local DuckieBank_CurrentRequestTable=nil;
local DuckieBank_NewRequests=false;
local DuckieBank_CollateEvent="None";
local DuckieBank_NetHook=nil;
local DuckieBank_SnapshotTimer=0;

local DB_TYPE_BANKER   = nil;
local DB_TYPE_REQUEST  = 1;
local DB_TYPE_SETTINGS = 2;
local DuckieBank_Header = {
	VerData=0,			-- Data version
	VerSender=0,		-- Sender version
	VerBanker=0,		-- Banker version of the addon that made the original snapshot
	Banker=nil,			-- Currently receiving banker
	Stamp=0,			-- The accompanying stamp
	Notes="",			-- Banker's notes
	Player=nil,			-- Player name
	Type=nil;			-- No special type, so normal banker
};
local DuckieBank_TransmitPressed=nil;
local DuckieBank_Timers = {
	Last=0,					-- Running heartbeat timekeeper
	NextPoll=600,			-- Not important. Wont run yet anyway.
	NextSend=-1,			-- Not active
	StuffIt=-1,				-- Not active
	UpdateNotified=false,	-- Upcoming update not notified
	LastInStamp=0,			-- Last incoming data
	CollateBump=-1,
	BumpSendRequests=-1;
	BumpSendSettings=-1;
};
local DuckieBank_Poll = {
	Section="Requests",
	Index=0,
};
local DuckieBank_RL = {};
local DuckieBank_NewStuff={};
local DuckieBank_Loader = {
	index=1,
	entry=1,
};


DBT_INACTIVITY_NOTIFY = 10;			-- 10 seconds before broadcast will notify on upcoming update
DBT_INACTIVITY_DELAY = 2;			-- 2 seconds of event delay
DBT_INACTIVITY_SEND = 180;			-- 3 minutes inactivity in the bank and bags will send new contents
DBT_INTERVAL_POLL = 3600;			-- Not important. Unused at first.
DBT_INTERVAL_POLLREST = 1800;		-- You're done all, so rest a bit
DBT_INACTIVITY_BANKER = 1296000;	-- (60x60x24x15) 15 days old banker-data will be deleted
DBT_REQUEST_DEATH = 604800;			-- (60x60x24x7) 7 days
DBT_RESEND_DELAY = 5;				-- Delay when attempting to send again
DBT_RESEND_MAXIMUM = 120;			-- Max time for attempting resend before DuckNet reset
local DB_HEARTBEAT = 0.3;

local DB_BANKERRANK="banker";

local DBT_ARMOR=nil;
local DBT_CONSUMABLE=nil;
local DBT_CONTAINER=nil;
local DBT_REAGENT=nil;
local DBT_RECIPE=nil;
local DBT_PROJECTILE=nil;
local DBT_QUEST=nil;
local DBT_QUIVER=nil;
local DBT_TRADEGOODS=nil;
local DBT_WEAPON=nil;

local DBV_ABORT = 0;
local DBV_RETRY = 1;
local DBV_OKAY  = 2;

-- Set up for handling
function DuckieBank_OnLoad()
	DuckieBank_Ready=false;
	DuckieBank_LastGuild=nil;							-- Thanks to Baldmuffin for the heads-up on this one
	DuckieBank_DisplayNeedInit=true;
	this:RegisterEvent("VARIABLES_LOADED");
	this:RegisterEvent("BANKFRAME_OPENED");				-- Bank opened
	this:RegisterEvent("BANKFRAME_CLOSED");				-- Bank closed
	this:RegisterEvent("PLAYERBANKSLOTS_CHANGED");		-- Bank 28x changed
	this:RegisterEvent("PLAYERBANKBAGSLOTS_CHANGED");	-- Bank 7-bag changed
	this:RegisterEvent("BAG_UPDATE");					-- Player-bags updated
	this:RegisterEvent("GUILD_ROSTER_UPDATE");			-- Local guild-roster cache updated
	this:RegisterEvent("GUILD_INVITE_REQUEST");			-- Request to join a guild

	SlashCmdList["DUCKIEBANK"]=function(msg) DuckieBank_Slasher(msg) end;
	tinsert(UISpecialFrames,"DuckieBank_AFrame");
	tinsert(UISpecialFrames,"DuckieBank_BankerFrame");

	math.randomseed(time()); math.random(); math.random(); math.random();

	DuckieBank_SendBankerButton:Hide();
	DuckieBank_FillCostVBuy:Hide(); DuckieBank_FillCostVSell:Hide(); DuckieBank_ClearAllCosts:Hide();
	DuckieBank_EditCustomCost:Hide(); DuckieBank_OpenBankerButton:Hide();

	DuckieBank_RequestInfo:SetText(DUCKIEBANK_UITEXT_REQUESTITEM);
	DuckieBank_ABankerNotesHeader:SetText(DUCKIEBANK_UITEXT_BANKERNOTES);


--	DuckieBank_Chat("Loaded");
end


	-- Note that we're not using the accurate form. The reason is that this code
	-- does not need accurate timing, and this way has a much bigger chance of
	-- pausing if any hick-ups should occur elsewhere - thus increasing the chance
	-- of correct functionality.
-- The heartbeat
function DuckieBank_HeartBeat(elapsed)
	if (DuckieBank_NetHook) then DuckieBank_NetHook(elapsed); end	-- The network driver depends on infusion from me
	DuckieBank_Timers.Last=DuckieBank_Timers.Last+elapsed;			-- Update this
	if (DuckieBank_Timers.Last<DB_HEARTBEAT) then return; end;		-- Heartbeat is 500ms
	DuckieBank_Timers.Last=0;										-- Restart
	if (DuckieBank_Ready~=true) then DuckieBank_BootBank(); return; end

	if (DuckieBank_LastGuild and DuckieBank_Loader.index>0) then
		if (not DuckieBank_LastGuild[DuckieBank_Loader.index] or not DuckieBank_LastGuild[DuckieBank_Loader.index].Banker) then
			DuckieBank_Loader.index=0; DuckieBank_Loader.entry=1;
			if (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: Cache done"); end
			return;
		end;
		if (DuckieBank_LS.Debug and DuckieBank_Loader.index==1 and DuckieBank_Loader.entry==1) then DuckieBank_Chat("DEBUG: Running cache"); end
		local tp=DuckieBank_LastGuild[DuckieBank_LastGuild[DuckieBank_Loader.index].Banker];	-- Banker base root
		if (not tp) then DuckieBank_Loader.index=0; DuckieBank_Loader.entry=1; return; end;
		if (not tp[DuckieBank_Loader.entry]) then DuckieBank_Loader.index=DuckieBank_Loader.index+1; DuckieBank_Loader.entry=1; return; end;
		if (DuckieBank_CacheItem(tp[DuckieBank_Loader.entry].link)) then DuckieBank_Loader.entry=DuckieBank_Loader.entry+1; end
	end

	if (DuckieBank_Banker and DuckieBank_AFrame:IsVisible()) then
		if (DuckNet_CanTransmit(DUCKIEBANK_PREFIX)) then
			DuckieBank_AInfoText:SetText(DUCKIEBANK_VERSIONTEXT);
			DuckieBank_AInfoText:SetTextColor(1,1,1);
		else
			DuckieBank_AInfoText:SetText(DUCKIEBANK_TEXT_NETBUSY);
			DuckieBank_AInfoText:SetTextColor(1,1,0);
		end
	end

--[[		Poll-dispatcher		]]
	DuckieBank_Timers.NextPoll=DuckieBank_Timers.NextPoll-DB_HEARTBEAT;							-- Update timer
	if (DuckieBank_Timers.NextPoll<0) then														-- It's time
		if (not DuckNet_Idle(DUCKIEBANK_PREFIX)) then DuckieBank_BumpPoll(); return; end		-- Net is busy, so stall a bit
		local collateevent=nil; if (DuckieBank_CollateEvent=="Running") then collateevent=true; DuckieBank_CollateEvent="None"; end
		marker,stamp=DuckieBank_NextPoll(collateevent);
		if (marker and stamp) then
			if (DuckieBank_Poll.Section=="Bankers" and time()-stamp>DBT_INACTIVITY_BANKER) then		-- The banker is too old
				if (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: Removing banker "..DuckieBank_LastGuild[DuckieBank_Poll.Index].Banker..", as it is outdated"); end
				DuckieBank_RemoveBanker(DuckieBank_Poll.Index);
			else																								-- Poll network for this banker
				if (DuckNet_Poll(DUCKIEBANK_PREFIX,stamp,marker,DUCKIEBANK_VERSION_BANKNUM)==false) then
					if (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: Poll not initiated by DuckNet. Most likely other network-actions are active. Bumping..."); end
					DuckieBank_BumpPoll();
				end
			end
		end
	end

--[[		Snapshot-dispatcher		]]
	if (DuckieBank_Timers.StuffIt>=0) then			-- Activated
		DuckieBank_Timers.StuffIt=DuckieBank_Timers.StuffIt-DB_HEARTBEAT;
		if (DuckieBank_Timers.StuffIt<0) then
			DuckieBank_Timers.StuffIt=-1;
			DuckieBank_SaveItems();
		end
	end

--[[		Banker-dispatcher		]]
	if (DuckieBank_Timers.NextSend>0) then			-- Activated
		DuckieBank_Timers.NextSend=DuckieBank_Timers.NextSend-DB_HEARTBEAT;

		-- I'm a banker and my data is up next, so notify that it's gonna go in a few seconds
		if (DuckieBank_Banker and UnitName("player")==DuckieBank_BankerSend) then
			if (DuckieBank_Timers.NextSend<=DBT_INACTIVITY_NOTIFY and DuckieBank_Timers.UpdateNotified==false) then
				DuckieBank_Timers.UpdateNotified=true,	        	-- Upcoming update notified
				DuckieBank_Chat(string.format(DUCKIEBANK_NOTIFY_BROADCAST,DBT_INACTIVITY_NOTIFY));
			end
		end

		-- Time to send the stuff
		if (DuckieBank_Timers.NextSend<=0) then							-- Do it
			DuckieBank_Timers.NextSend=-1;								-- Timer done
			if (not DuckieBank_SendBank(DuckieBank_BankerSend)) then	-- Send the specified bank's contents
				if (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: Unable to send data. Waiting "..DBT_RESEND_DELAY.." seconds"); end
				DuckieBank_Timers.NextSend=DBT_RESEND_DELAY;			-- Bump send-timer
				DuckieBank_SendDelayed=DuckieBank_SendDelayed+DBT_RESEND_DELAY;
				if (DuckieBank_SendDelayed>DBT_RESEND_MAXIMUM) then
					DuckieBank_SendDelayed=0;
					DuckieBank_Ready=DuckieBank_Connect(true);			-- Perform silent unregister and re-register
					if (not DuckieBank_Ready) then return; end
					if (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: Re-registered with DuckNet"); end
				end
			else
				DuckieBank_Timers.NextSend=-1;							-- Timer done
				DuckieBank_SendDelayed=0;
			end
		end
	end

--[[		Collate bumper			]]
	if (DuckieBank_Timers.CollateBump>=0) then
		DuckieBank_Timers.CollateBump=DuckieBank_Timers.CollateBump-DB_HEARTBEAT;
		if (DuckieBank_Timers.CollateBump<0) then
			DuckieBank_Timers.CollateBump=-1;
			DuckieBank_CollateMyRequests();
		end
	end

--[[		Send-request bumper		]]
	if (DuckieBank_Timers.BumpSendRequests>=0) then
		DuckieBank_Timers.BumpSendRequests=DuckieBank_Timers.BumpSendRequests-DB_HEARTBEAT;
		if (DuckieBank_Timers.BumpSendRequests<0) then
			if (DuckieBank_SendRequests()~=DBV_RETRY) then DuckieBank_Timers.BumpSendRequests=-1; end
		end
	end

--[[		Send-settings bumper		]]
	if (DuckieBank_Timers.BumpSendSettings>=0) then
		DuckieBank_Timers.BumpSendSettings=DuckieBank_Timers.BumpSendSettings-DB_HEARTBEAT;
		if (DuckieBank_Timers.BumpSendSettings<0) then
			if (DuckieBank_SendSettings()~=DBV_RETRY) then DuckieBank_Timers.BumpSendSettings=-1; end
		end
	end
end


function DuckieBank_UpdateRoster()
	if (not IsInGuild()) then return; end
	GuildRoster();
end


function DuckieBank_BootBank()
	if (not DuckieBank_DataCache) then DuckieBank_DataCache=DuckieBank_Enumerate(); if (DuckieBank_DataCache) then DuckieBank_PopulateFilterDD(); end return; end
	DuckieBank_UpdateRoster();
	local state=DuckieBank_BankerState(); if (not state) then return; end;						-- Can't get the info yet
	DuckieBank_LastGuild=DuckieBank_TableSection(); if (not DuckieBank_LastGuild) then return; end
	DuckieBank_DBValidate232();

	if (DuckieBank_LastGuild["VerData"]) then
		if (DuckieBank_LastGuild["VerData"]<tonumber(DUCKIEBANK_VERSION_DATA)) then
			DuckNet_ClearTable(DuckieBank_LastGuild);
			DuckieBank_Chat(DUCKIEBANK_NOTIFY_WRONGVERSION);
		end
	end

	-- Do banker-mode
	if (state=="Banker") then
		DuckieBank_Banker=true;
		DuckieBank_Monitor=false;
		DuckieBank_Chat(DUCKIEBANK_NOTIFY_BANKERON);
		DuckieBank_SendBankerButton:Show();
		DuckieBank_ClearAllCosts:Show();
		DuckieBank_OpenBankerButton:Show();
		if (Auctioneer and Auctioneer.API and Auctioneer.API.GetVendorBuyPrice and Auctioneer.API.GetVendorSellPrice) then
			DuckieBank_FillCostVBuy:Show(); DuckieBank_FillCostVSell:Show();
		end
	else
		if (state=="Monitor") then DuckieBank_Monitor=true; end
		DuckieBank_Banker=false;
		DuckieBank_Chat(DUCKIEBANK_NOTIFY_READY);
		DuckieBank_SendBankerButton:Hide();
	end
	DuckieBank_PopulateDD();

	DuckieBank_Ready=DuckieBank_Connect();					-- Connect to the data-channel
	if (not DuckieBank_Ready) then return; end;
	DuckieBank_SnapshotTimer=DBT_INACTIVITY_DELAY;

	DuckieBank_CleanupRequests();
	DuckieBank_Timers.NextPoll=20;
	DBT_INTERVAL_POLL=math.random(240,300);
	DuckieBank_CollateEvent="Running";							-- Force update of requests first
	DuckieBank_Loader.index=1; DuckieBank_Loader.entry=1;		-- Start it

	return;
end


function DuckieBank_NextPoll(pollrequests)
	if (not DuckieBank_LastGuild) then return nil,nil; end
	local bankers=DuckieBank_CountBankers();
	local requests=DuckieBank_CountRequestBlocks();
	if (bankers<1 and requests<1) then return nil,nil; end

	DuckieBank_Timers.NextPoll=DBT_INTERVAL_POLL;
	DuckieBank_Poll.Index=DuckieBank_Poll.Index+1;

	if (pollrequests and requests>0) then									-- Force update of requests
		DuckieBank_Poll.Section="Requests";
		DuckieBank_Poll.Index=1;											-- One less to accomodate for next increase
	elseif (DuckieBank_Poll.Section=="Requests") then
		if (DuckieBank_Poll.Index>requests) then							-- Wrap
			if (bankers>0) then DuckieBank_Poll.Section="Bankers"; end
			DuckieBank_Poll.Index=1;
		end
	elseif (DuckieBank_Poll.Section=="Bankers") then
		if (DuckieBank_Poll.Index>bankers) then			-- Wrap
 			if (requests>0) then DuckieBank_Poll.Section="Requests"; end
			DuckieBank_Poll.Index=1;
		end
	else																	-- Wrap
		DuckieBank_Poll.Section="Requests"; DuckieBank_Poll.Index=1;
	end

	if (bankers>0 and DuckieBank_Poll.Section=="Bankers" and DuckieBank_Poll.Index==bankers) then		-- About to poll last banker
		DuckieBank_Timers.NextPoll=DBT_INTERVAL_POLLREST;				-- Please dont change this as it spams the server. May cause lag (and even DC in worst case)
		if (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: Resting for "..(DBT_INTERVAL_POLLREST / 60).." minutes after this poll"); end
	end

	-- Make request-type markers: DUCKIEBANK_REQUEST,banker,player
	if (DuckieBank_Poll.Section=="Requests") then marker,player,stamp=DuckieBank_GetRequestBlockInfo(DuckieBank_Poll.Index); marker=DUCKIEBANK_REQUEST..","..marker..","..player;
	elseif (DuckieBank_Poll.Section=="Bankers") then
		marker=DuckieBank_LastGuild[DuckieBank_Poll.Index].Banker;
		stamp=DuckieBank_LastGuild[DuckieBank_Poll.Index].Stamp;
		if (string.find(marker,",")) then DuckieBank_RemoveBanker(DuckieBank_Poll.Index); return nil,nil; end	-- Remains from mix with v2.20
	else return nil,nil; end
	if (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: Polling "..marker.." @ "..stamp); end

	return marker,stamp;
end


function DuckieBank_BumpPoll(now)
	if (not now) then
		if (DuckieBank_Timers.NextPoll>60) then return; end			-- At least 60 seconds remaining, so leave it
		now=DuckieBank_Timers.NextPoll;								-- Make offset
	else
		now=time();													-- Recalc to offset from now
	end
	DuckieBank_Timers.NextPoll=now+math.random(20,60);				-- Set new jittery poll-timer
end


function DuckieBank_CacheItem(item)
	if (type(item)=="number") then item="item:"..item;
	elseif (type(item)~="string") then return true; end
	local name=GetItemInfo(item);
	if (not GameTooltip:IsVisible()) then
		if (not name and DuckieBank_CacheRetries<10) then
			GameTooltip:SetOwner(DuckieBank_HiddenButton); GameTooltip:SetHyperlink(item); GameTooltip:Hide();
			DuckieBank_CacheRetries=DuckieBank_CacheRetries+1;
			return false;
		else
			DuckieBank_CacheRetries=0;
		end
	end
	return true;
end


function DuckieBank_Enumerate()
	_,_,_,_,_,DBT_ARMOR=GetItemInfo(24686);			if (not DBT_ARMOR) then DuckieBank_CacheItem(24686); return false; end
	_,_,_,_,_,DBT_CONSUMABLE=GetItemInfo(22829);	if (not DBT_CONSUMABLE) then DuckieBank_CacheItem(22829); return false; end
	_,_,_,_,_,DBT_CONTAINER=GetItemInfo(5765);		if (not DBT_CONTAINER) then DuckieBank_CacheItem(5765); return false; end
	_,_,_,_,_,DBT_REAGENT=GetItemInfo(22576);		if (not DBT_REAGENT) then DuckieBank_CacheItem(22576); return false; end
	_,_,_,_,_,DBT_RECIPE=GetItemInfo(28282);		if (not DBT_RECIPE) then DuckieBank_CacheItem(28282); return false; end
	_,_,_,_,_,DBT_PROJECTILE=GetItemInfo(8069);		if (not DBT_PROJECTILE) then DuckieBank_CacheItem(8069); return false; end
	_,_,_,_,_,DBT_QUEST=GetItemInfo(31812);			if (not DBT_QUEST) then DuckieBank_CacheItem(31812); return false; end
	_,_,_,_,_,DBT_QUIVER=GetItemInfo(11362);		if (not DBT_QUIVER) then DuckieBank_CacheItem(11362); return false; end
	_,_,_,_,_,DBT_TRADEGOODS=GetItemInfo(23112);	if (not DBT_TRADEGOODS) then DuckieBank_CacheItem(23112); return false; end
	_,_,_,_,_,DBT_WEAPON=GetItemInfo(25306);		if (not DBT_WEAPON) then DuckieBank_CacheItem(25306); return false; end

	if (DuckieBank_LS.Debug) then
		if (not DBT_ARMOR) then DuckieBank_Chat("Could not enumerate DBT_ARMOR"); end
		if (not DBT_CONSUMABLE) then DuckieBank_Chat("Could not enumerate DBT_CONSUMABLE"); end
		if (not DBT_CONTAINER) then DuckieBank_Chat("Could not enumerate DBT_CONTAINER"); end
		if (not DBT_REAGENT) then DuckieBank_Chat("Could not enumerate DBT_REAGENT"); end
		if (not DBT_RECIPE) then DuckieBank_Chat("Could not enumerate DBT_RECIPE"); end
		if (not DBT_PROJECTILE) then DuckieBank_Chat("Could not enumerate DBT_PROJECTILE"); end
		if (not DBT_QUEST) then DuckieBank_Chat("Could not enumerate DBT_QUEST"); end
		if (not DBT_QUIVER) then DuckieBank_Chat("Could not enumerate DBT_QUIVER"); end
		if (not DBT_TRADEGOODS) then DuckieBank_Chat("Could not enumerate DBT_TRADEGOODS"); end
		if (not DBT_WEAPON) then DuckieBank_Chat("Could not enumerate DBT_WEAPON"); end
	end

	return true;
end


-- An event has been received
function DuckieBank_OnEvent(event)
	-- Check basic stuff needed
	if (event=="VARIABLES_LOADED") then
		if (DuckieBank_LS.IconX and DuckieBank_LS.IconY) then DuckieBank_SetIconAbsolute(DuckieBank_LS.IconX,DuckieBank_LS.IconY);
		else DuckieBank_SetIconAngle(DuckieBank_LS.IconPosition); end
		DuckieBank_DataLoaded=true;
	end
	if (DuckieBank_Ready~=true) then return; end

	if (event=="BANKFRAME_OPENED") then
		DuckieBank_SnapshotTimer=0;
		if (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: Snapshot set to "..DuckieBank_SnapshotTimer.." seconds"); end
	elseif (event=="BANKFRAME_CLOSED") then
		DuckieBank_SnapshotTimer=DBT_INACTIVITY_DELAY;
		if (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: Snapshot set to "..DuckieBank_SnapshotTimer.." seconds"); end
	end

	-- Any required update from the Banker
	if (event=="BANKFRAME_OPENED" or event=="PLAYERBANKSLOTS_CHANGED" or event=="PLAYERBANKBAGSLOTS_CHANGED" or event=="BAG_UPDATE") then
		DuckieBank_Timers.StuffIt=DuckieBank_SnapshotTimer;
		return;
	end

	if (event=="PLAYER_GUILD_UPDATE") then
		if (not IsInGuild()) then
			DuckieBank_Ready=false;
			DuckieBank_LastGuild=nil;
		end
		return;
	end

	if (event=="GUILD_ROSTER_UPDATE" and DuckieBank_Ready) then DuckieBank_ValidateRoster(); return; end
end


function DuckieBank_ValidateRoster()
	local seq=1;
	local ranks=nil;
	local name,rank,_,_,_,_,pnote,onote=GetGuildRosterInfo(seq);

	-- Check ranks
	while(name and rank) do
		rank=string.lower(rank);
		if (rank==DB_BANKERRANK) then ranks=true; break; end
		seq=seq+1;
		name,rank,_,_,_,_,pnote,onote=GetGuildRosterInfo(seq);
	end

	if (ranks) then
		local i=1;
		while (DuckieBank_LastGuild[i] and not DuckieBank_LastGuild[i].Banker) do
			seq=1;
			local name,rank,_,_,_,_,pnote,onote=GetGuildRosterInfo(seq);
			while(name and rank) do
				rank=string.lower(rank);
				if (name==DuckieBank_LastGuild[i].Banker and rank~=DB_BANKERRANK) then DuckieBank_RemoveBanker(i,true); break; end
				seq=seq+1;
				name,rank,_,_,_,_,pnote,onote=GetGuildRosterInfo(seq);
			end
			i=i+1;
		end
	end

	if (not DuckieBank_LS.NotAgressive) then
		local i=1;
		while (DuckieBank_LastGuild[i]) do
			if (not DuckieBank_LastGuild[i].Banker) then return; end
			seq=1;
			local found=nil;
			local name,rank,_,_,_,_,pnote,onote=GetGuildRosterInfo(seq);
			while(name and rank) do
				if (name==DuckieBank_LastGuild[i].Banker) then found=true; end
				seq=seq+1;
				name,rank,_,_,_,_,pnote,onote=GetGuildRosterInfo(seq);
			end
			if (not found) then DuckieBank_RemoveBanker(i,true); end
			i=i+1;
		end
	end
end


-- Make update for show
function DuckieBank_Frame_OnShow()
	PlaySound("igMainMenuOpen");
	-- Flag it if it's not
	if (not DuckieBank_AFrame.hasShown) then DuckieBank_AFrame.hasShown=1; end
	DuckieBank_SetWindowState();
end


function DuckieBank_BankerFrame_OnShow()
	if (not DuckieBank_BankerFrame_Requests:IsVisible() and not DuckieBank_BankerFrame_Options:IsVisible()) then
		DuckieBank_ShowRequests();
	end
end


function DuckieBank_ShowRequests()
	DuckieBank_BankerFrame_Options:Hide();
	DuckieBank_BankerFrame_Requests:Show();
end


function DuckieBank_ShowOptions()
	DuckieBank_BankerFrame_Options:Show();
	DuckieBank_BankerFrame_Requests:Hide();
end


function DuckieBank_SetWindowState()
	if (DuckieBank_Banker) then
		DuckieBank_SendBankerButton:Show();
		DuckieBank_ClearAllCosts:Show();
		DuckieBank_OpenBankerButton:Show();
		if (Auctioneer and Auctioneer.API and Auctioneer.API.GetVendorBuyPrice and Auctioneer.API.GetVendorSellPrice) then
			DuckieBank_FillCostVBuy:Show(); DuckieBank_FillCostVSell:Show();
		end
	else
		DuckieBank_SendBankerButton:Hide();
		DuckieBank_ClearAllCosts:Hide();
		DuckieBank_OpenBankerButton:Hide();
		DuckieBank_FillCostVBuy:Hide(); DuckieBank_FillCostVSell:Hide();
	end
end


-- Clean-up and fixing
function DuckieBank_Frame_OnHide()
	if (DuckieBank_NewRequests) then DuckieBank_SendRequests(); end
end


-- There's slashing to be done
function DuckieBank_Slasher(msg,quiet)
	if (DuckieBank_Ready~=true) then return; end;

-- Validate input
	if (not msg) then msg=""; end
	if (strlen(msg)>0) then msg=strlower(msg); end

-- Check command
	DuckieBank_LastGuild=DuckieBank_TableSection();

	-- Reset frame positions
	if (msg=="resetpos") then DuckieBank_ResetPos(); return; end

	-- Kill entire register
	if (msg=="killall") then
		DuckNet_ClearTable(DuckieBank_LastGuild);
		if (not quiet) then DuckieBank_Chat(DUCKIEBANK_NOTIFY_KILLALL); end
		return;
	end

	if (msg=="clean") then DuckieBank_UpdateRoster(); return; end

	if (msg=="agressive") then
		if (not DuckieBank_LS.NotAgressive) then
			DuckieBank_LS.NotAgressive=true;
			DuckieBank_Chat(DUCKIEBANK_NOTIFY_AGRESSIVE_ON);
		else
			DuckieBank_LS.NotAgressive=nil;
			DuckieBank_Chat(DUCKIEBANK_NOTIFY_AGRESSIVE_OFF);
		end
		return;
	end

	if (msg=="debug") then
		if (DuckieBank_LS.Debug~=true) then DuckieBank_LS.Debug=true; DuckieBank_Chat("DEBUG: ON");
		else DuckieBank_LS.Debug=false; DuckieBank_Chat("DEBUG: OFF"); end
		return;
	end

	if (msg=="debug server") then
		if (DuckieBank_LS.DebugServer~=true) then DuckieBank_LS.DebugServer=true; DuckieBank_Chat("DEBUG: Verbose server ON");
		else DuckieBank_LS.DebugServer=false; DuckieBank_Chat("DEBUG: Verbose server OFF"); end
		return;
	end

	-- Banker-specific switches
	if (DuckieBank_Banker) then
		-- Money display
		if (string.sub(msg,1,5)=="money") then
			if (msg=="money on") then
				DuckieBank_LS.ShowMoney=true;
				DuckieBank_SaveItems();
				if (not quiet) then DuckieBank_Chat("-> "..DUCKIEBANK_NOTIFY_MONEY_ON); end
				return;
			elseif (msg=="money off") then
				DuckieBank_LS.ShowMoney=false;
				DuckieBank_SaveItems();
				if (not quiet) then DuckieBank_Chat("-> "..DUCKIEBANK_NOTIFY_MONEY_OFF); end
				return;
			end
		end
		-- Bag status
		if (string.sub(msg,1,3)=="bag") then
			local bagnum=tonumber(string.sub(msg,4,4));
			if (bagnum and bagnum>=0 and bagnum<=4) then
				local state=string.sub(msg,6);
				if (state=="on" or state=="off") then
					if (state=="on") then state=true; else state=nil; end
					DuckieBank_LS["Bag"..bagnum]=state;
				end
			end
			if (quiet) then return; end

			if (not DuckieBank_LS.Bag0 and not DuckieBank_LS.Bag1 and not DuckieBank_LS.Bag2 and not DuckieBank_LS.Bag3 and not DuckieBank_LS.Bag4) then
				DuckieBank_Chat("-> "..DUCKIEBANK_NOTIFY_BAGS_NONE);
			elseif (DuckieBank_LS.Bag0 and DuckieBank_LS.Bag1 and DuckieBank_LS.Bag2 and DuckieBank_LS.Bag3 and DuckieBank_LS.Bag4) then
				DuckieBank_Chat("-> "..DUCKIEBANK_NOTIFY_BAGS_ALL);
			else
				DuckieBank_Chat("-> "..DUCKIEBANK_NOTIFY_BAGS_NUMBERING);
				local text;
				if (DuckieBank_LS.Bag0) then text=DUCKIEBANK_NOTIFY_BAGS_INCLUDED; else text=DUCKIEBANK_NOTIFY_BAGS_EXCLUDED; end
				DuckieBank_Chat("-> "..DUCKIEBANK_NOTIFY_BAGS_PREFIX0..text..DUCKIEBANK_NOTIFY_BAGS_POSTFIX);
				if (DuckieBank_LS.Bag1) then text=DUCKIEBANK_NOTIFY_BAGS_INCLUDED; else text=DUCKIEBANK_NOTIFY_BAGS_EXCLUDED; end
				DuckieBank_Chat("-> "..DUCKIEBANK_NOTIFY_BAGS_PREFIX1..text..DUCKIEBANK_NOTIFY_BAGS_POSTFIX);
				if (DuckieBank_LS.Bag2) then text=DUCKIEBANK_NOTIFY_BAGS_INCLUDED; else text=DUCKIEBANK_NOTIFY_BAGS_EXCLUDED; end
				DuckieBank_Chat("-> "..DUCKIEBANK_NOTIFY_BAGS_PREFIX2..text..DUCKIEBANK_NOTIFY_BAGS_POSTFIX);
				if (DuckieBank_LS.Bag3) then text=DUCKIEBANK_NOTIFY_BAGS_INCLUDED; else text=DUCKIEBANK_NOTIFY_BAGS_EXCLUDED; end
				DuckieBank_Chat("-> "..DUCKIEBANK_NOTIFY_BAGS_PREFIX3..text..DUCKIEBANK_NOTIFY_BAGS_POSTFIX);
				if (DuckieBank_LS.Bag4) then text=DUCKIEBANK_NOTIFY_BAGS_INCLUDED; else text=DUCKIEBANK_NOTIFY_BAGS_EXCLUDED; end
				DuckieBank_Chat("-> "..DUCKIEBANK_NOTIFY_BAGS_PREFIX4..text..DUCKIEBANK_NOTIFY_BAGS_POSTFIX);
			end
			return;
		end
		-- Send data
		if (msg=="send") then
			DuckieBank_BankerSend=UnitName("player");
			DuckieBank_Timers.UpdateNotified=true;
			DuckieBank_Timers.NextSend=3;			-- Send in 3 seconds
			return;
		end
		-- Price stacking option
		if (msg=="stack") then
			if (DuckieBank_LS.SingleCost) then
				DuckieBank_LS.SingleCost=nil;
				if (not quiet) then DuckieBank_Chat("-> "..DUCKIEBANK_NOTIFY_STACK_MULTIPLY); end
			else
				DuckieBank_LS.SingleCost=true;
				if (not quiet) then DuckieBank_Chat("-> "..DUCKIEBANK_NOTIFY_STACK_SINGLE); end
			end
			return;
		end
	end

	-- Brief help triggered by any unknown (or empty) instruction
	DuckieBank_Chat(DUCKIEBANK_VERSIONTEXT);
	DuckieBank_Chat(SLASH_DUCKIEBANK2.." ("..SLASH_DUCKIEBANK1..")"..DUCKIEBANK_HELP_HEADER);
	DuckieBank_Chat("-> "..SLASH_DUCKIEBANK2.." resetpos :  "..DUCKIEBANK_HELP_RESETPOS);
	DuckieBank_Chat("-> "..SLASH_DUCKIEBANK2.." killall :  "..DUCKIEBANK_HELP_KILLALL);
	if (DuckieBank_Banker) then
		DuckieBank_Chat("-> "..SLASH_DUCKIEBANK2.." money ON|OFF :  "..DUCKIEBANK_HELP_MONEYONOFF);
		DuckieBank_Chat("-> "..SLASH_DUCKIEBANK2.." stack :  "..DUCKIEBANK_HELP_STACK);
		DuckieBank_Chat("-> "..SLASH_DUCKIEBANK2.." send :  "..DUCKIEBANK_HELP_SEND);
		DuckieBank_Chat("-> "..SLASH_DUCKIEBANK2.." bag :  "..DUCKIEBANK_HELP_BAG);
		DuckieBank_Chat("-> "..SLASH_DUCKIEBANK2.." bag<0-4> ON|OFF :  "..DUCKIEBANK_HELP_BAGnONOFF.."\"/db bag4 off\")");
	end
end


function DuckieBank_OptionsSave()
	DuckieBank_BankerFrame_Options_KillAllConfirm:Hide();
	if (this==DuckieBank_BankerFrame_Options_KillAllConfirm) then DuckNet_ClearTable(DuckieBank_LastGuild); return; end

	DuckieBank_LS.Bag0=DuckieBank_BankerFrame_Options_Bag0:GetChecked();
	DuckieBank_LS.Bag1=DuckieBank_BankerFrame_Options_Bag1:GetChecked();
	DuckieBank_LS.Bag2=DuckieBank_BankerFrame_Options_Bag2:GetChecked();
	DuckieBank_LS.Bag3=DuckieBank_BankerFrame_Options_Bag3:GetChecked();
	DuckieBank_LS.Bag4=DuckieBank_BankerFrame_Options_Bag4:GetChecked();
	DuckieBank_LS.ShowMoney=DuckieBank_BankerFrame_Options_Money:GetChecked();
	if (DuckieBank_BankerFrame_Options_StackMultiply:GetChecked()) then DuckieBank_LS.SingleCost=false; else DuckieBank_LS.SingleCost=true; end
	DuckieBank_Timers.StuffIt=DuckieBank_SnapshotTimer;
	DuckieBank_Ready=false;
end


function DuckieBank_ShowOptionsFrame()
	DuckieBank_BankerFrame_Options_KillAllConfirm:Hide();
	DuckieBank_BankerFrame_Options_Bag0:SetChecked(DuckieBank_LS.Bag0);
	DuckieBank_BankerFrame_Options_Bag1:SetChecked(DuckieBank_LS.Bag1);
	DuckieBank_BankerFrame_Options_Bag2:SetChecked(DuckieBank_LS.Bag2);
	DuckieBank_BankerFrame_Options_Bag3:SetChecked(DuckieBank_LS.Bag3);
	DuckieBank_BankerFrame_Options_Bag4:SetChecked(DuckieBank_LS.Bag4);

	DuckieBank_BankerFrame_Options_Money:SetChecked(DuckieBank_LS.ShowMoney);
	if (DuckieBank_LS.SingleCost) then DuckieBank_BankerFrame_Options_StackMultiply:SetChecked(false); else DuckieBank_BankerFrame_Options_StackMultiply:SetChecked(true); end
end


function DuckieBank_DBValidate232()
	local seq=1;
	while(DuckieBank_LastGuild[seq]) do
		if (not DuckieBank_LastGuild[seq].Banker) then DuckieBank_RemoveBanker(seq);
		else
			if (string.find(DuckieBank_LastGuild[seq].Banker,",")) then DuckieBank_RemoveBanker(seq);
			else
				if (not DuckieBank_LastGuild[seq].Stamp) then DuckieBank_LastGuild[seq].Stamp=time()-(DBT_INACTIVITY_BANKER-172800); end	-- 2 days
				if (not DuckieBank_LastGuild[DuckieBank_LastGuild[seq].Banker]) then DuckieBank_LastGuild[DuckieBank_LastGuild[seq].Banker]={}; end
				seq=seq+1;
			end
		end
	end
end


-- Banker notes handling
function DuckieBank_BankerTextChanged()
	if (DuckieBank_Banker~=true) then return; end;
	if (not DuckieBank_BankerSelectDropDown.selectedID) then return; end
	if (not DuckieBank_LastGuild[DuckieBank_BankerSelectDropDown.selectedID]) then return; end;
	if (not DuckieBank_LastGuild[DuckieBank_BankerSelectDropDown.selectedID].Banker) then return; end;
	if (DuckieBank_LastGuild[DuckieBank_BankerSelectDropDown.selectedID].Banker~=UnitName("player")) then return; end;

	local index=DuckieBank_BankerIndex(UnitName("player")); if (not index) then return; end;
	index.Notes=DuckieBank_BankerNotesEdit:GetText();
end

function DuckieBank_BankerTextEscape()
	if (DuckieBank_Banker~=true) then return; end;
	DuckieBank_BankerNotesEdit:SetText(DuckieBank_OldBankerNotes);
	DuckieBank_BankerNotesEdit:ClearFocus();
	if (DuckieBank_Banker~=true) then return; end;
	local index=DuckieBank_BankerIndex(UnitName("player"));
	if (not index) then return; end;
	index.Notes=DuckieBank_OldBankerNotes;
end


-- The resetting-bissniss
function DuckieBank_ResetPos()
	DuckieBank_AFrame:ClearAllPoints();
	DuckieBank_AFrame:SetPoint("TOPLEFT","UIParent","TOPLEFT",50,-104);
end


-- Return pointer to this banker's price-list
function DuckieBank_BankerCost()
	if (not DuckieBank_Banker) then return nil; end;
	local bankername=UnitName("player");
	if (not DuckieBank_LS[bankername]) then DuckieBank_LS[bankername]={}; end;
	return DuckieBank_LS[bankername];
end

-- Return pointer to a banker's items
function DuckieBank_BankerItems(bankername)
	if (not bankername) then
		if (not DuckieBank_Banker) then return nil; end;
		bankername=UnitName("player");
	end;
	if (DuckieBank_LastGuild[bankername]) then return DuckieBank_LastGuild[bankername]; end;

	return nil;
end

-- Return pointer to a banker's contents-index
function DuckieBank_BankerIndex(bankername,create)
	if (not DuckieBank_LastGuild) then return nil; end;
	if (not bankername) then
		if (not DuckieBank_Banker) then return nil; end;
		bankername=UnitName("player");
	end;
	local i=1;
	while (DuckieBank_LastGuild[i]) do
		if (not DuckieBank_LastGuild[i].Banker) then break; end;
		if (DuckieBank_LastGuild[i].Banker==bankername) then return DuckieBank_LastGuild[i]; end;
		i=i+1;
	end

	if (create==true) then
		DuckieBank_LastGuild[i]={};
		DuckieBank_LastGuild[i].Banker=bankername;
		DuckieBank_LastGuild[bankername]={};
		return DuckieBank_LastGuild[i];
	end;

	return nil;
end


function DuckieBank_CheckLocalBankerData()
	local name=UnitName("player");
	if (not DuckieBank_LS[name]) then DuckieBank_LS[name]={}; end;
	if (not DuckieBank_LS[name].BankItemCount) then DuckieBank_LS[name].BankItemCount=0; end;
	if (not DuckieBank_LS[name].Cost) then DuckieBank_LS[name].Cost={}; end;
end


function DuckieBank_RegisterItem(seq,icon,count,link,splitsecond)
	DuckieBank_LS[DuckieBank_BankerSend][seq]={ ["icon"]=icon, ["count"]=count, ["link"]=link };
	DuckieBank_LS[DuckieBank_BankerSend][seq].cost=DuckieBank_GetItemLocalCost(DuckieBank_LS[DuckieBank_BankerSend][seq]);
	if (not splitsecond) then
		DuckieBank_LS.Age[link]=nil;
		return;
	end
	link=DuckieBank_GetUniqueItem(link);
	if (not DuckieBank_LS.Age) then DuckieBank_LS.Age={}; end
	if (not DuckieBank_LS.Age[link]) then DuckieBank_LS.Age[link]={}; end
	DuckieBank_LS.Age[link].found=true;
	if (not DuckieBank_LS.Age[link].Time) then DuckieBank_LS.Age[link].Time=splitsecond; end	-- Don't have it, so set date
end


function DuckieBank_SetItemsUnfound()
	if (not DuckieBank_LS.Age) then return; end

	local seq=0;
	local link=nil;
	for num=1,28 do link=GetContainerItemLink(BANK_CONTAINER,num); if (link) then seq=seq+1; break; end end
	if (seq==0) then
		for num=5,11 do
			local bagslots=GetContainerNumSlots(num);
			for item=1,bagslots do link=GetContainerItemLink(num,item); if (link) then seq=seq+1; break; end end
		end
	end

	if (seq>0) then		-- At bank
		for link,lTable in pairs(DuckieBank_LS.Age) do lTable.found=nil; end		-- Unfound all
	else				-- Not at bank
		local ThisBanker=DuckieBank_LS[DuckieBank_BankerSend]; if (not ThisBanker) then return; end
		local offset=DuckieBank_LS[DuckieBank_BankerSend].BankItemCount;
		for link,lTable in pairs(DuckieBank_LS.Age) do
			-- Set bag-contents unfound this scan
			seq=1; 
			while (ThisBanker[seq+offset]) do												-- Traverse previous bag-contents
				local here=ThisBanker[seq+offset];
				link=DuckieBank_GetUniqueItem(here.link);
				if (DuckieBank_LS.Age[link]) then DuckieBank_LS.Age[link].found=nil; end	-- It's already stamped, so set unfound
				seq=seq+1;
			end
			-- Set bank-contents found this scan since bank is not open. We would assume it's not gone while we've not visited it
			-- Doing this after bags will also ensure that overlapping bank- and carry-items wont conflict
			seq=1;
			while (seq<=offset and ThisBanker[seq]) do										-- Traverse last known bank-contents
				local here=ThisBanker[seq];
				link=DuckieBank_GetUniqueItem(here.link);
				if (DuckieBank_LS.Age[link]) then DuckieBank_LS.Age[link].found=true; end
				seq=seq+1;
			end
		end
	end
end


function DuckieBank_RemoveDeadItems()
	if (not DuckieBank_LS.Age) then return; end
	for link,lTable in pairs(DuckieBank_LS.Age) do if (not lTable.found) then DuckieBank_LS.Age[link]=nil; end end
end


-- Save bank in DB
function DuckieBank_SaveItems()
	if (not DuckieBank_Banker) then return; end;			-- None existing
	if (DuckieBank_Banker==false) then return; end;			-- Don't save if it's not the banker
	now=time();

	DuckieBank_Timers.UpdateNotified=false;
	DuckieBank_BankerSend=UnitName("player");
	DuckieBank_Timers.NextSend=DBT_INACTIVITY_SEND;			-- Set send timer

	-- Make stuff to work with
	local index=DuckieBank_BankerIndex(nil,true);		-- Create it if it's not there
	local ThisBanker=DuckieBank_BankerItems(nil,true);	-- Create it if it's not there
	local link,icon,quantity,bagslots;

	DuckieBank_CheckLocalBankerData();					-- Check (and make if need be) the local stuff for a banker

	-- Save some stuff about environment
	index.Stamp=time();						-- Make stamp
	index.VerData=tonumber(DUCKIEBANK_VERSION_DATA);
	index.VerBanker=tonumber(DUCKIEBANK_VERSION_BANKNUM);
	
	if (not DuckieBank_LS[DuckieBank_BankerSend]) then DuckieBank_LS[DuckieBank_BankerSend]={}; end
	if (DuckieBank_LS.ShowMoney) then index.Money=math.floor(GetMoney()/10000); else index.Money=nil; end

	DuckieBank_SetItemsUnfound();

	-- Do all bank slots
	local seq=0;
	for num=1,28 do
		link=GetContainerItemLink(BANK_CONTAINER,num);
		icon,quantity=GetContainerItemInfo(BANK_CONTAINER,num);
		if (link) then
			seq=seq+1;			-- Advance with 1-offset
			DuckieBank_RegisterItem(seq,icon,quantity,link,now);
		end
	end

	-- Each banker bag
	for num=5,11 do
		bagslots=GetContainerNumSlots(num);
		-- Each slot in said bag
		link=nil;
		for item=1,bagslots do
			link=GetContainerItemLink(num,item);
			icon,quantity=GetContainerItemInfo(num,item);
			if (link) then
				seq=seq+1;			-- Advance with 1-offset
				DuckieBank_RegisterItem(seq,icon,quantity,link,now);
			end
		end
	end
	if (seq>0) then DuckieBank_LS[DuckieBank_BankerSend].BankItemCount=seq; end

	-- Update in case bank is not open
	seq=DuckieBank_LS[DuckieBank_BankerSend].BankItemCount;
	-- Each player bag
	for num=0,4 do
		if (DuckieBank_LS["Bag"..num]) then
			bagslots=GetContainerNumSlots(num);
			-- Each slot in said bag
			link=nil;
			for item=1,bagslots do
				link=GetContainerItemLink(num,item);
				icon,quantity=GetContainerItemInfo(num,item);
				if (link) then
					seq=seq+1;			-- Advance with 1-offset
					DuckieBank_RegisterItem(seq,icon,quantity,link,now);
				end
			end
		end
	end

	-- Clear any following data
	while(DuckieBank_LS[DuckieBank_BankerSend][seq+1]) do seq=seq+1; DuckieBank_LS[DuckieBank_BankerSend][seq]=nil; end

	DuckieBank_RemoveDeadItems();					-- Remove items not in inventory

	-- Copy and sort
	DuckieBank_LastGuild[DuckieBank_BankerSend]=nil; DuckieBank_LastGuild[DuckieBank_BankerSend]=DuckNet_CopyTable(DuckieBank_LS[DuckieBank_BankerSend]);
	ThisBanker=DuckieBank_BankerItems(nil,true);	-- Create it if it's not there

	-- Squeeze
	seq=1;
	while(ThisBanker[seq]) do
		local pos=seq+1;
		local newcount=ThisBanker[seq].count;
		while(ThisBanker[pos]) do
			if (DuckieBank_GetUniqueItem(ThisBanker[seq].link)==DuckieBank_GetUniqueItem(ThisBanker[pos].link)) then
				newcount=newcount+ThisBanker[pos].count;
				local move=pos+1;
				while(ThisBanker[move]) do
					ThisBanker[move-1]=DuckNet_CopyTable(ThisBanker[move]);
					move=move+1;
				end
				ThisBanker[move-1]=nil;
			else pos=pos+1; end
		end
		ThisBanker[seq].count=newcount;
		seq=seq+1;
	end

--local itemName, itemLink, itemRarity, itemLevel, itemMinLevel, itemType, itemSubType, itemStackCount, itemEquipLoc, itemTexture = GetItemInfo(itemID or "itemString" or "itemName" or "itemLink") 
	-- Sort by level
	seq=1;
	while(ThisBanker[seq+1]) do
		local pos=seq;
		local _,_,_,_,level1=GetItemInfo(ThisBanker[seq].link);
		local _,_,_,_,level2=GetItemInfo(ThisBanker[seq+1].link);
		if (not level2) then level2=level1; elseif (not level1) then level1=level2; end
		if (level1 and level2) then								-- Do not attempt if an item is uncached
			while (pos>0 and level1>level2) do
				DuckieBank_Swap(ThisBanker,pos,pos+1);
				pos=pos-1;
				if (pos>0) then
					_,_,_,_,level1=GetItemInfo(ThisBanker[pos].link);
					_,_,_,_,level2=GetItemInfo(ThisBanker[pos+1].link);
					if (not level2) then level2=level1; elseif (not level1) then level1=level2; end
				end
			end
		end
		seq=seq+1;
	end

	-- Sort by subtype (mail, cloth, plate, etc - ench, tailoring, etc)
	seq=1;
	while(ThisBanker[seq+1]) do
		local pos=seq;
		local _,_,_,_,_,_,sub1=GetItemInfo(ThisBanker[seq].link);
		local _,_,_,_,_,_,sub2=GetItemInfo(ThisBanker[seq+1].link);
		if (not sub2) then sub2=sub1; elseif (not sub1) then sub1=sub2; end
		if (sub1 and sub2) then								-- Do not attempt if an item is uncached
			while (pos>0 and sub1>sub2) do
				DuckieBank_Swap(ThisBanker,pos,pos+1);
				pos=pos-1;
				if (pos>0) then
					_,_,_,_,_,_,sub1=GetItemInfo(ThisBanker[pos].link);
					_,_,_,_,_,_,sub2=GetItemInfo(ThisBanker[pos+1].link);
					if (not sub2) then sub2=sub1; elseif (not sub1) then sub1=sub2; end
				end
			end
		end
		seq=seq+1;
	end

	-- Sort by type
	seq=1;
	while(ThisBanker[seq+1]) do
		local pos=seq;
		local _,_,_,_,_,itemType1=GetItemInfo(ThisBanker[seq].link);
		local _,_,_,_,_,itemType2=GetItemInfo(ThisBanker[seq+1].link);
		if (not itemType2) then itemType2=itemType1; elseif (not itemType1) then itemType1=itemType2; end
		if (itemType1 and itemType2) then								-- Do not attempt if an item is uncached
			while (pos>0 and itemType1>itemType2) do
				DuckieBank_Swap(ThisBanker,pos,pos+1);
				pos=pos-1;
				if (pos>0) then
					_,_,_,_,_,itemType1=GetItemInfo(ThisBanker[pos].link);
					_,_,_,_,_,itemType2=GetItemInfo(ThisBanker[pos+1].link);
					if (not itemType2) then itemType2=itemType1; elseif (not itemType1) then itemType1=itemType2; end
				end
			end
		end
		seq=seq+1;
	end

	-- Update is needed
	if (DuckieBank_AFrame:IsVisible()) then
		DuckieBank_StuffBankItems();
	end
end


function DuckieBank_Swap(bank,pos1,pos2)
	local temp=DuckNet_CopyTable(bank[pos1]);
	bank[pos1]=DuckNet_CopyTable(bank[pos2]);
	bank[pos2]=DuckNet_CopyTable(temp);
end


function DuckieBank_MouseWheel(slider)
	if (not slider) then slider=this; end
	local min,max=slider:GetMinMaxValues(); if (not min or not max) then return; end
	local step=slider:GetValueStep(); if (not step) then return; end
	local value=slider:GetValue(); if (not value) then return; end
	if (arg1==1 and (value-step)>=min) then slider:SetValue(value-step);
	elseif (arg1==-1 and (value+step)<=max) then slider:SetValue(value+step); end
end


function DuckieBank_GetItemOverlay(link)
	_,_,_,_,_,itemType,itemSubType=GetItemInfo(link);
	if (DuckieBank_FilterSelectDropDown.selectedID>1 and itemType) then
		if (itemType~=DuckieBank_FilterName(DuckieBank_FilterSelectDropDown.selectedID)) then return "Interface\\Buttons\\UI-QuickslotRed" end
	end
	local marking="Interface\\AddOns\\DuckieBank\\Images\\Frame_";
	if (itemType==DBT_ARMOR) then marking=marking..ITEMCOLORRED;
	elseif (itemType==DBT_CONSUMABLE) then marking=marking..ITEMCOLORCYAN;
	elseif (itemType==DBT_CONTAINER) then marking=marking..ITEMCOLORGREEN;
	elseif (itemType==DBT_REAGENT) then marking=marking..ITEMCOLORPURPLE;
	elseif (itemType==DBT_RECIPE) then marking=marking..ITEMCOLORPINK;
	elseif (itemType==DBT_PROJECTILE) then marking=marking..ITEMCOLORBLUE;
	elseif (itemType==DBT_QUEST) then marking=marking..ITEMCOLORYELLOW;
	elseif (itemType==DBT_QUIVER) then marking=marking..ITEMCOLORGREEN;
	elseif (itemType==DBT_TRADEGOODS) then marking=marking..ITEMCOLORORANGE;
	elseif (itemType==DBT_WEAPON) then marking=marking..ITEMCOLORBLUE;
	else marking=nil; end
	return marking;
end


	-- There seem to be some problems with the setting of
	-- the players guild-name (it's slow), so this "LastGuild"
	-- test will provide a better UI, as it will not stuff things in
	-- there if it's not loaded yet.
-- Build it
function DuckieBank_StuffBankItems()
	DuckieBank_LastGuild=DuckieBank_TableSection();
	if (not DuckieBank_LastGuild) then
		if (DuckieBank_Ready) then DuckieBank_Chat(DUCKIEBANK_NOTIFY_NODATABASE,1); end
		return;
	end

	local dropframe=DuckieBank_BankerSelectDropDown;
	if (not dropframe) then return;
	elseif (not dropframe.selectedID) then
		UIDropDownMenu_SetSelectedID(dropframe,1);
		if (not dropframe.selectedID) then return; end;
	end
	if (not DuckieBank_LastGuild[dropframe.selectedID]) then return; end;
	if (not DuckieBank_LastGuild[dropframe.selectedID].Banker) then return; end;

	local texture,button;

	-- Find selected banker's stuff
	local selecteddude=DuckieBank_LastGuild[dropframe.selectedID].Banker;
	UIDropDownMenu_SetText(selecteddude,dropframe)

	local index=DuckieBank_BankerIndex(selecteddude); if (not index) then return; end;			-- Get this
	local ThisBanker=DuckieBank_BankerItems(selecteddude); if (not ThisBanker) then return; end;	-- And this

	if (index.Notes) then
		DuckieBank_OldBankerNotes=index.Notes;
		DuckieBank_BankerNotesEdit:SetText(index.Notes);
	else
		DuckieBank_OldBankerNotes="";
		DuckieBank_BankerNotesEdit:SetText("");
	end
	
	if (index.Money) then
		DuckieBank_ABankerMoney:Show();
		DuckieBank_ABankerMoney:SetText(DUCKIEBANK_TEXT_MONEY..": "..index.Money..DUCKIEBANK_ABR_GOLD);
	else
		DuckieBank_ABankerMoney:Hide();
	end

	local slider=DuckieBank_ItemSlider;		-- The slider
	local slidemax=0;
	local amount=table.maxn(ThisBanker);
	if (amount>40) then
		slidemax=amount-40;							-- 41-40=1
		slidemax=math.ceil(slidemax/8);				-- 1/8=1
		slidemax=slidemax*8;						-- 1x8=8
	end
	slider:SetMinMaxValues(0,slidemax);

	-- Update time and date
	local timestring="";
	if (not index.Stamp or index.Stamp<1) then timestring="";
	else timestring=date("%a, %d. %b",index.Stamp); end
	DuckieBank_AInfoText:SetText(DUCKIEBANK_VERSIONTEXT);
	DuckieBank_ABankerInfo:SetText(timestring);

	-- The new bank
	local seq=1; 
	local offset=slider:GetValue();

	while (seq<41 and ThisBanker[seq+offset]) do
		local here=ThisBanker[seq+offset];
		getglobal("DuckieBank_AItem"..seq.."Group"):SetTexture(DuckieBank_GetItemOverlay(here.link));
		button=getglobal("DuckieBank_AItem"..seq);
		SetItemButtonTexture(button,here.icon);
		SetItemButtonCount(button,here.count);
		if (DuckieBank_Banker and selecteddude==UnitName("player")) then
			getglobal("DuckieBank_AItem"..seq.."ItemCost"):SetText(DuckieBank_GetItemLocalCost(here));
		elseif (here.cost) then getglobal("DuckieBank_AItem"..seq.."ItemCost"):SetText(here.cost);
		else getglobal("DuckieBank_AItem"..seq.."ItemCost"):SetText(""); end;
		seq=seq+1;
	end

	-- fill empty
	while (seq<41) do
		button=getglobal("DuckieBank_AItem"..seq);
		SetItemButtonTexture(button,"");
		SetItemButtonCount(button,nil);
		getglobal("DuckieBank_AItem"..seq.."Group"):SetTexture("");
		getglobal("DuckieBank_AItem"..seq.."ItemCost"):SetText("");
		seq=seq+1;
	end
end


function DuckieBank_GetItemLocalCost(itemT,onlylink)
	local Cost=DuckieBank_BankerCost();					-- Get cost-list
	local item={};

	if (itemT) then item=itemT;
	elseif (onlylink) then item.link=onlylink; item.count=1;
	else return ""; end

	local link=DuckieBank_GetUniqueItem(item.link);
	if (not Cost[link]) then return ""; end;

	if (Cost[link].Type=="Money") then return DuckieBank_FormatItemCost(Cost[link].Cost,item.count);
	elseif (Cost[link].Type=="Custom" and Cost[link].Cost) then return tostring(Cost[link].Cost); end;
	return "";
end


-- Only to be called from a button
function DuckieBank_Button_SpewPrices()
	if (not DuckieBank_Banker) then return; end;
	local index=DuckieBank_BankerIndex(UnitName("player")); if (not index) then return; end;
	index.Stamp=time();						-- Make new stamp
	local Cost=DuckieBank_BankerCost();
	if (not Cost) then return; end;
	if (this~=DuckieBank_FillCostVBuy and this~=DuckieBank_FillCostVSell) then return; end

	-- Update price-list
	local ThisBanker=DuckieBank_BankerItems(UnitName("player"));									-- Get me
	local seq=1; while (ThisBanker[seq]) do
		local itemID=DuckieBank_GetUniqueItem(ThisBanker[seq].link);
		if (not Cost[itemID]) then Cost[itemID]={}; end;
		if (Cost[itemID].Cost==nil or Cost[itemID].Cost=="") then									-- Only empty slots
			Cost[itemID].Cost=DuckieBank_GetItemVendorCost(ThisBanker[seq],this);					-- Spew raw cost-data
			Cost[itemID].Type="Money";
		end
		seq=seq+1;
	end

	DuckieBank_Timers.UpdateNotified=false;
	DuckieBank_BankerSend=UnitName("player");
	DuckieBank_Timers.NextSend=DBT_INACTIVITY_SEND;			-- Set send timer
	DuckieBank_StuffBankItems();
end


function DuckieBank_Button_ClearAllCosts()
	if (not DuckieBank_Banker) then return; end;
	local index=DuckieBank_BankerIndex(UnitName("player")); if (not index) then return; end;
	index.Stamp=time();						-- Make new stamp
	local Cost=DuckieBank_BankerCost();
	if (not Cost) then return; end;
	if (this~=DuckieBank_ClearAllCosts) then return; end

	-- Update price-list
	local ThisBanker=DuckieBank_BankerItems(UnitName("player"));									-- Get me
	local seq=1; while (ThisBanker[seq]) do
		local itemID=DuckieBank_GetUniqueItem(ThisBanker[seq].link);
		if (Cost[itemID]) then Cost[itemID]=nil; end
		seq=seq+1;
	end

	DuckieBank_Timers.UpdateNotified=false;
	DuckieBank_BankerSend=UnitName("player");
	DuckieBank_Timers.NextSend=DBT_INACTIVITY_SEND;			-- Set send timer
	DuckieBank_StuffBankItems();
end


function DuckieBank_GetItemVendorCost(item,button)
	-- If using auctioneer prices
	if (Auctioneer and Auctioneer.API) then
		local itemID=DuckieBank_GetID(item.link);
		if (itemID) then
			if (button==DuckieBank_FillCostVBuy) then
				if (Auctioneer and Auctioneer.API and Auctioneer.API.GetVendorBuyPrice) then return Auctioneer.API.GetVendorBuyPrice(itemID); end;
			end
			if (button==DuckieBank_FillCostVSell) then
				if (Auctioneer and Auctioneer.API and Auctioneer.API.GetVendorSellPrice) then  return Auctioneer.API.GetVendorSellPrice(itemID); end;
			end
			return nil;
		end
	end
	return nil;
end


function DuckieBank_FormatItemCost(cost,mpx)
	if (not cost or cost<1) then return ""; end;
	if (not mpx) then mpx=1; end;
	if (DuckieBank_LS.SingleCost) then mpx=1; end

	-- When price is money
	cost=cost*mpx;
	local g=floor(cost/10000); cost=cost-(g*10000);
	local s=floor(cost/100); cost=cost-(s*100);
	local c=cost;

	-- If both gold and copper, round coppers
	if (g>0 and c>0) then if (c>=50) then s=s+1; if (s==100) then s=0; g=g+1; end; end; c=0; end;

	local text="";
	if (g>0) then text=text..g..DUCKIEBANK_ABR_GOLD; end;
	if (s>0) then text=text..s..DUCKIEBANK_ABR_SILVER; end;
	if (c>0) then text=text..c..DUCKIEBANK_ABR_COPPER; end;
	return text;
end


function DuckieBank_GetUniqueItem(link)
	if (type(link)~="string") then return nil; end;
	itemID,i1,i2,i3,i4,i5,i6=link:match("|Hitem:(%p?%d+):(%p?%d+):(%p?%d+):(%p?%d+):(%p?%d+):(%p?%d+):(%p?%d+):(%p?%d+)|h%[(.-)%]|h");
	return "item:"..itemID..":"..i1..":"..i2..":"..i3..":"..i4..":"..i5..":"..i6;
end

function DuckieBank_GetID(link)
	if (type(link)~="string") then return nil; end;
	local itemid=link:match("|Hitem:(%p?%d+):(%p?%d+):(%p?%d+):(%p?%d+):(%p?%d+):(%p?%d+):(%p?%d+):(%p?%d+)|h%[(.-)%]|h");
	return tonumber(itemid);
end


-- Find the banker button thingie
function DuckieBank_Button_Selector()
	if (not DuckieBank_LastGuild) then return nil; end;
	local dropframe=DuckieBank_BankerSelectDropDown;
	if (not dropframe) then return nil;						-- It's not there
	elseif (not dropframe.selectedID) then					-- None selected
		UIDropDownMenu_SetSelectedID(dropframe,1);			-- Select first entry
	end

	if (not DuckieBank_LastGuild[dropframe.selectedID]) then return nil; end;
	if (not DuckieBank_LastGuild[dropframe.selectedID].Banker) then return nil; end;
	local ThisBanker=DuckieBank_BankerItems(DuckieBank_LastGuild[dropframe.selectedID].Banker);
	if (not ThisBanker) then return nil; end

	local slider=DuckieBank_ItemSlider;		-- The slider
	local value=ThisBanker[this:GetID()+slider:GetValue()];
    return value;
end

-- Banker-button mouseover
function DuckieBank_Button_OnEnter()
	if (not DuckieBank_LastGuild) then return; end;
	if (this==DuckieBank_FillCostVBuy or this==DuckieBank_FillCostVSell or this==DuckieBank_ClearAllCosts) then
		local dropframe=DuckieBank_BankerSelectDropDown;
		if (not DuckieBank_LastGuild[dropframe.selectedID]) then return; end;
		if (not DuckieBank_LastGuild[dropframe.selectedID].Banker) then return; end;
		if (DuckieBank_LastGuild[dropframe.selectedID].Banker==UnitName("player")) then
			local text=" ";
			if (this==DuckieBank_FillCostVBuy) then text=DUCKIEBANK_HOVER_COSTVBUY;
			elseif (this==DuckieBank_FillCostVSell) then text=DUCKIEBANK_HOVER_COSTVSELL;
			elseif (this==DuckieBank_ClearAllCosts) then text=DUCKIEBANK_HOVER_COSTCLEAR;
			end
			GameTooltip:SetOwner(this,"ANCHOR_RIGHT");
			GameTooltip:SetText(text);
			GameTooltip:AddLine("("..DUCKIEBANK_HOVER_COSTSETCUSTOM..")");
			GameTooltip:Show();
		end
		return;
	elseif (this==DuckieBank_RequestItem) then
		GameTooltip:SetOwner(this,"ANCHOR_RIGHT");
		GameTooltip:SetText(DUCKIEBANK_HOVER_REQUESTEDITEM);
		GameTooltip:AddLine(DUCKIEBANK_HOVER_REQUESTEDITEMLC1);
		GameTooltip:AddLine(DUCKIEBANK_HOVER_REQUESTEDITEMLC2);
		GameTooltip:AddLine(DUCKIEBANK_HOVER_REQUESTEDITEMSLC);
		GameTooltip:Show();
		return;
	elseif (this==DuckieBank_BankerFrame_RequestsItem1 or this==DuckieBank_BankerFrame_RequestsItem2 or
			this==DuckieBank_BankerFrame_RequestsItem3 or this==DuckieBank_BankerFrame_RequestsItem4) then
		local item=nil;
		if (this==DuckieBank_BankerFrame_RequestsItem1) then item=DuckieBank_BankerFrame_RequestsItem1Remove.Item;
		elseif (this==DuckieBank_BankerFrame_RequestsItem2) then item=DuckieBank_BankerFrame_RequestsItem2Remove.Item;
		elseif (this==DuckieBank_BankerFrame_RequestsItem3) then item=DuckieBank_BankerFrame_RequestsItem3Remove.Item;
		elseif (this==DuckieBank_BankerFrame_RequestsItem4) then item=DuckieBank_BankerFrame_RequestsItem4Remove.Item;
		end
		if (item) then
			local ThisItem=DuckieBank_RL[item].itemID;
			GameTooltip:SetOwner(this,"ANCHOR_RIGHT");
			if (not string.find(ThisItem,":")) then _,ThisItem=GetItemInfo(ThisItem);  end
			GameTooltip:SetHyperlink(ThisItem);
		end
		return;
	end

	if (not ChatFrameEditBox:IsVisible()) then SetCursor("BUY_CURSOR"); end
	local value=DuckieBank_Button_Selector();
	if (not value) then return; end;

	if (value.link and strlen(value.link)>0) then
		GameTooltip:SetOwner(this,"ANCHOR_RIGHT");
		GameTooltip:SetHyperlink(value.link);
		local age=DuckieBank_GetItemAge(value.link);
		if (age and DuckieBank_Banker) then
			GameTooltip:AddLine(DUCKIEBANK_TEXT_ITEMAGE.." "..age);
			local numlines=GameTooltip:NumLines();
			local thetext=getglobal("GameTooltipTextLeft"..numlines);
			thetext:SetTextColor(1,1,1);
			GameTooltip:SetHeight(GameTooltip:GetHeight()+thetext:GetHeight());
			local width=thetext:GetWidth();
			if (width>GameTooltip:GetWidth()) then GameTooltip:SetWidth(width+25); end
--			GameTooltip:SetMinimumWidth(width+25);
		end
	end
end


function DuckieBank_GetItemAge(link)
	link=DuckieBank_GetUniqueItem(link);
	if (not DuckieBank_LS.Age) then return nil; end
	if (not DuckieBank_LS.Age[link]) then return nil; end
	if (not DuckieBank_LS.Age[link].Time) then return nil; end
	return SecondsToTime(time()-DuckieBank_LS.Age[link].Time,true);
end


-- Option-check clicked
function DuckieBank_OptionsCheck_OnClick()
	if (this==DuckieBank_BankerFrame_Options_BankerRank) then
		DuckieBank_BankerFrame_Options_BankerONote:SetChecked(false);
		DuckieBank_BankerFrame_Options_BankerPNote:SetChecked(false);
	elseif (this==DuckieBank_BankerFrame_Options_BankerONote) then
		DuckieBank_BankerFrame_Options_BankerRank:SetChecked(false);
		DuckieBank_BankerFrame_Options_BankerPNote:SetChecked(false);
	elseif (this==DuckieBank_BankerFrame_Options_BankerPNote) then
		DuckieBank_BankerFrame_Options_BankerRank:SetChecked(false);
		DuckieBank_BankerFrame_Options_BankerONote:SetChecked(false);
	end
end


-- Banker-button clicked
function DuckieBank_Button_OnClick(arg1)
	local value=DuckieBank_Button_Selector();
	if (not value) then return; end;

	if (arg1=="LeftButton") then
		if (IsControlKeyDown()) then DressUpItemLink(value.link);												-- Dress-up
		elseif (ChatFrameEditBox:IsVisible() and IsShiftKeyDown()) then ChatFrameEditBox:Insert(value.link);	-- Link
		elseif (IsShiftKeyDown()) then																			-- Buy a stack
			DuckieBank_SetBuyItem(value,-1);
		else																									-- Buy one
			DuckieBank_SetBuyItem(value,1);
		end
	elseif (arg1=="RightButton") then
		if (not DuckieBank_Banker) then return; end;
		DuckieBank_EditCustomCost:Show();
		local text="";
		if (value.cost) then text=value.cost; end
		ResetCursor(); GameTooltip:Hide();
		DuckieBank_EditCustomCost:ClearAllPoints();
		DuckieBank_EditCustomCost:SetPoint("TOP",this,"TOP");
		DuckieBank_EditCustomCost:SetText(text);
		DuckieBank_EditCustomCost:SetFocus();
		DuckieBank_EditItem=DuckNet_CopyTable(value);
		DuckieBank_EditItem.Button=this;
		this:Hide();
	end
end


function DuckieBank_SetBuyItem(item,add)
	local itemID=item.link;
	if (not itemID) then
		DuckieBank_RequestItemIconTexture:SetTexture("");
		SetItemButtonCount(button,0);
		DuckieBank_RequestItem.id=nil;
		DuckieBank_RequestItem.stackcount=0;
		return;
	end

	if (not DuckieBank_RequestItem.id) then DuckieBank_RequestItem.id=itemID; DuckieBank_RequestItem.stackcount=0; end
	if (not DuckieBank_RequestItem.stackcount) then DuckieBank_RequestItem.stackcount=0; end
	if (DuckieBank_RequestItem.id==itemID) then DuckieBank_RequestItem.stackcount=DuckieBank_RequestItem.stackcount+add;
	else DuckieBank_RequestItem.id=itemID; DuckieBank_RequestItem.stackcount=1; end
	if (DuckieBank_RequestItem.stackcount<1) then DuckieBank_RequestItem.stackcount=1; end
	DuckieBank_RequestItemIconTexture:SetTexture(item.icon);
	SetItemButtonCount(DuckieBank_RequestItem,DuckieBank_RequestItem.stackcount);

	local name,_,rarity=GetItemInfo(item.link); 
	r,g,b=1,1,1;
	if (type(rarity)=="number" and rarity<7) then r,g,b=GetItemQualityColor(rarity); end				-- Further test for uncached items
	DuckieBank_RequestItemName:SetText(name);
	DuckieBank_RequestItemName:SetTextColor(r,g,b);
end


-- tp -> player
-- tp -> empty slot
function DuckieBank_ValidateRequest(banker,onlyplayer,generate)
	if (not DuckieBank_LastGuild.Requests) then DuckieBank_LastGuild.Requests={}; end
	if (not DuckieBank_LastGuild.Requests[banker]) then DuckieBank_LastGuild.Requests[banker]={}; end
	if (not generate) then generate=UnitName("player"); end
	if (not DuckieBank_LastGuild.Requests[banker][generate]) then DuckieBank_LastGuild.Requests[banker][generate]={}; end
	if (onlyplayer) then return DuckieBank_LastGuild.Requests[banker][generate]; end
	local seq=1; while(DuckieBank_LastGuild.Requests[banker][generate][seq]) do seq=seq+1; end
	DuckieBank_LastGuild.Requests[banker][generate][seq]={};
	return DuckieBank_LastGuild.Requests[banker][generate][seq];
end


function DuckieBank_ShowRequestFrame()
	DuckieBank_CleanupRequests();
	DuckieBank_BankerFrame_RequestsItem1IconTexture:SetTexture("");
	DuckieBank_BankerFrame_RequestsItem1Item:SetText(""); DuckieBank_BankerFrame_RequestsItem1Player:SetText(""); DuckieBank_BankerFrame_RequestsItem1Notes:SetText("");
	DuckieBank_BankerFrame_RequestsItem1Remove.Item=nil;
	DuckieBank_BankerFrame_RequestsItem2IconTexture:SetTexture("");
	DuckieBank_BankerFrame_RequestsItem2Item:SetText(""); DuckieBank_BankerFrame_RequestsItem2Player:SetText(""); DuckieBank_BankerFrame_RequestsItem2Notes:SetText("");
	DuckieBank_BankerFrame_RequestsItem2Remove.Item=nil;
	DuckieBank_BankerFrame_RequestsItem3IconTexture:SetTexture("");
	DuckieBank_BankerFrame_RequestsItem3Item:SetText(""); DuckieBank_BankerFrame_RequestsItem3Player:SetText(""); DuckieBank_BankerFrame_RequestsItem3Notes:SetText("");
	DuckieBank_BankerFrame_RequestsItem3Remove.Item=nil;
	DuckieBank_BankerFrame_RequestsItem4IconTexture:SetTexture("");
	DuckieBank_BankerFrame_RequestsItem4Item:SetText(""); DuckieBank_BankerFrame_RequestsItem4Player:SetText(""); DuckieBank_BankerFrame_RequestsItem4Notes:SetText("");
	DuckieBank_BankerFrame_RequestsItem4Remove.Item=nil;
	DuckieBank_UpdateRequests();
end


function DuckieBank_BuildRequestList()
	if (not DuckieBank_CurrentRequestTable) then return; end
	DuckNet_ClearTable(DuckieBank_RL);
	local count=0;
	local seq;
	for player,pt in pairs(DuckieBank_CurrentRequestTable) do
		seq=1;
		while(pt[seq]) do
			if (not pt[seq].Removed) then
				count=count+1;
				DuckieBank_RL[count]={};
				DuckieBank_RL[count].Player=player;
				DuckieBank_RL[count].itemID=pt[seq].itemID;
				DuckieBank_RL[count].Count=pt[seq].Count;
				DuckieBank_RL[count].Stamp=pt[seq].Stamp;
				DuckieBank_RL[count].Notes=pt[seq].Notes;
			end
			seq=seq+1;
		end
	end

	seq=1;
	while(DuckieBank_RL[seq+1]) do
		local pos=seq;
		while (DuckieBank_RL[pos] and DuckieBank_RL[pos].Stamp>DuckieBank_RL[pos+1].Stamp) do
			local temp=DuckNet_CopyTable(DuckieBank_RL[pos]);
			DuckieBank_RL[pos]=DuckNet_CopyTable(DuckieBank_RL[pos+1]);
			DuckieBank_RL[pos+1]=DuckNet_CopyTable(temp);
			pos=pos-1;
		end
		seq=seq+1;
	end
end


function DuckieBank_UpdateRequests()
	if (not DuckieBank_Banker) then return; end
	DuckieBank_LastGuild=DuckieBank_TableSection(); if (not DuckieBank_LastGuild) then DuckieBank_Chat(DUCKIEBANK_NOTIFY_NODATABASE,1); return; end
	if (not DuckieBank_LastGuild.Requests) then return; end;
	local banker=UnitName("player");
	if (not DuckieBank_LastGuild.Requests[banker]) then return; end
	-- The slider setup
	local slidemax=DuckieBank_CountRequest(DuckieBank_LastGuild.Requests[banker])-4;
	if (slidemax<0) then slidemax=0; end
	DuckieBank_BankerFrame_RequestsSlider:SetMinMaxValues(0,slidemax);
	-- Draw
	DuckieBank_CurrentRequestTable=DuckieBank_LastGuild.Requests[banker];
	DuckieBank_BuildRequestList();
	DuckieBank_RedrawRequests();
end


function DuckieBank_RedrawRequests()
	if (not DuckieBank_CurrentRequestTable) then return; end
	local offset=DuckieBank_BankerFrame_RequestsSlider:GetValue();
	local count=0;
	local index=1;

	local seq=offset+1;
	while(DuckieBank_RL[seq]) do
		local name,_,rarity,_,_,itemType,itemSubType,_,_,Texture=GetItemInfo(DuckieBank_RL[seq].itemID); 
		r,g,b=1,1,1;
		if (type(rarity)=="number" and rarity<7) then r,g,b=GetItemQualityColor(rarity); end				-- Further test for uncached items
		getglobal("DuckieBank_BankerFrame_RequestsItem"..index.."IconTexture"):SetTexture(Texture);
		SetItemButtonCount(getglobal("DuckieBank_BankerFrame_RequestsItem"..index),DuckieBank_RL[seq].Count);
		getglobal("DuckieBank_BankerFrame_RequestsItem"..index.."Item"):SetText(name);
		getglobal("DuckieBank_BankerFrame_RequestsItem"..index.."Item"):SetTextColor(r,g,b);
		getglobal("DuckieBank_BankerFrame_RequestsItem"..index.."Player"):SetText(DuckieBank_RL[seq].Player.." ("..date("%a, %d. %b",DuckieBank_RL[seq].Stamp)..")");
		getglobal("DuckieBank_BankerFrame_RequestsItem"..index.."Notes"):SetText(DuckieBank_RL[seq].Notes);
		getglobal("DuckieBank_BankerFrame_RequestsItem"..index.."Remove").Item=seq;
		if (index==4) then return; end
		index=index+1;
		seq=seq+1;
	end
	if (index<5) then
		DuckieBank_BankerFrame_RequestsItem4IconTexture:SetTexture(""); DuckieBank_BankerFrame_RequestsItem4Remove.Item=nil;
		DuckieBank_BankerFrame_RequestsItem4Item:SetText(""); DuckieBank_BankerFrame_RequestsItem4Player:SetText(""); DuckieBank_BankerFrame_RequestsItem4Notes:SetText("");
		SetItemButtonCount(DuckieBank_BankerFrame_RequestsItem4,0);
	end
	if (index<4) then
		DuckieBank_BankerFrame_RequestsItem3IconTexture:SetTexture(""); DuckieBank_BankerFrame_RequestsItem3Remove.Item=nil;
		DuckieBank_BankerFrame_RequestsItem3Item:SetText(""); DuckieBank_BankerFrame_RequestsItem3Player:SetText(""); DuckieBank_BankerFrame_RequestsItem3Notes:SetText("");
		SetItemButtonCount(DuckieBank_BankerFrame_RequestsItem3,0);
	end
	if (index<3) then
		DuckieBank_BankerFrame_RequestsItem2IconTexture:SetTexture(""); DuckieBank_BankerFrame_RequestsItem2Remove.Item=nil;
		DuckieBank_BankerFrame_RequestsItem2Item:SetText(""); DuckieBank_BankerFrame_RequestsItem2Player:SetText(""); DuckieBank_BankerFrame_RequestsItem2Notes:SetText("");
		SetItemButtonCount(DuckieBank_BankerFrame_RequestsItem2,0);
	end
	if (index<2) then
		DuckieBank_BankerFrame_RequestsItem1IconTexture:SetTexture(""); DuckieBank_BankerFrame_RequestsItem1Remove.Item=nil;
		DuckieBank_BankerFrame_RequestsItem1Item:SetText(""); DuckieBank_BankerFrame_RequestsItem1Player:SetText(""); DuckieBank_BankerFrame_RequestsItem1Notes:SetText("");
		SetItemButtonCount(DuckieBank_BankerFrame_RequestsItem1,0);
	end
end


function DuckieBank_RemoveRequestClick()
	if (not this.Item) then return; end

	local pt=DuckieBank_CurrentRequestTable[DuckieBank_RL[this.Item].Player];
	local seq=1;
	while(pt[seq]) do
		if (not pt[seq].Removed) then
			if (pt[seq].itemID==DuckieBank_RL[this.Item].itemID and pt[seq].Count==DuckieBank_RL[this.Item].Count and pt[seq].Stamp==DuckieBank_RL[this.Item].Stamp) then
				pt[seq].Removed=true;
			end
		end
		seq=seq+1;
	end

	DuckieBank_ShowRequestFrame();
end


function DuckieBank_MakeRequest()
	DuckieBank_LastGuild=DuckieBank_TableSection(); if (not DuckieBank_LastGuild) then DuckieBank_Chat(DUCKIEBANK_NOTIFY_NODATABASE,1); return; end

	local dropframe=DuckieBank_BankerSelectDropDown;
	if (not dropframe) then return;
	elseif (not dropframe.selectedID) then UIDropDownMenu_SetSelectedID(dropframe,1); if (not dropframe.selectedID) then return; end
	end
	if (not DuckieBank_LastGuild[dropframe.selectedID]) then return; end;
	if (not DuckieBank_LastGuild[dropframe.selectedID].Banker) then return; end;

	if (not DuckieBank_RequestItem.id or not DuckieBank_RequestItem.stackcount or DuckieBank_RequestItem.stackcount<1) then return; end

	-- Save entry
	local now=time();
	local entry=DuckieBank_ValidateRequest(DuckieBank_LastGuild[dropframe.selectedID].Banker);		-- Make new request
	entry.itemID=DuckieBank_RequestItem.id;
	entry.Count=DuckieBank_RequestItem.stackcount;
	entry.Notes=DuckieBank_RequestNote:GetText();
	entry.Stamp=now;
	DuckieBank_NewRequests=true;
	DuckieBank_ValidateRequest(DuckieBank_LastGuild[dropframe.selectedID].Banker,true).Stamp=now;

	-- Clear contents
	DuckieBank_RequestItem.id=nil;
	DuckieBank_RequestItem.stackcount=nil;
	DuckieBank_RequestItemIconTexture:SetTexture("");
	SetItemButtonCount(DuckieBank_RequestItem,1);
	DuckieBank_RequestNote:SetText("");
	DuckieBank_RequestItemName:SetText("");
end


function DuckieBank_CustomCostChanged()
	DuckieBank_EditCustomCost:Hide(); if (not DuckieBank_EditItem) then return; end;
	DuckieBank_EditItem.Button:Show();
	local index=DuckieBank_BankerIndex(UnitName("player")); if (not index) then return; end;
	index.Stamp=time();						-- Make new stamp
	local Cost=DuckieBank_BankerCost(); if (not Cost) then return; end;	-- Get cost-list

	DuckieBank_EditItem.cost=DuckieBank_EditCustomCost:GetText();
	local itemID=DuckieBank_GetUniqueItem(DuckieBank_EditItem.link);
	if (not Cost[itemID]) then Cost[itemID]={}; end
	Cost[itemID].Cost=DuckieBank_EditItem.cost;
	Cost[itemID].Type="Custom";

	DuckieBank_Timers.UpdateNotified=false;
	DuckieBank_BankerSend=UnitName("player");
	DuckieBank_Timers.NextSend=DBT_INACTIVITY_SEND;			-- Set send timer
	DuckieBank_StuffBankItems();
end


function DuckieBank_CustomCostUnchanged()
	DuckieBank_EditCustomCost:Hide();
	if (not DuckieBank_EditItem) then return; end;
	DuckieBank_EditItem.Button:Show();
end


-- Generic chat-pane stuff
function DuckieBank_Chat(msg,r,g,b)
	if (DEFAULT_CHAT_FRAME) then
		if (not r and not g and not b) then r=1; g=0; b=1; end;
		if (not r) then r=0; end;
		if (not g) then g=0; end;
		if (not b) then b=0; end;
		DEFAULT_CHAT_FRAME:AddMessage("DuckieBank: "..msg,r,g,b);
	end
end

-- Duh
function DuckieBank_ToggleMainFrame()
	if (DuckieBank_Ready~=true) then return; end;
	if (DuckieBank_AFrame:IsVisible()) then
		DuckieBank_AFrame:Hide();
	else
		DuckieBank_StuffBankItems();
		DuckieBank_AFrame:Show();
	end
end

-- Force a transmission of my bank-stuff
function DuckieBank_SendBankerButtonClick()
	if (not DuckieBank_Banker) then return; end;
	local index=DuckieBank_BankerIndex(UnitName("player")); if (not index) then return; end;
	index.Stamp=time();						-- Make new stamp
	DuckieBank_Timers.UpdateNotified=true;
	DuckieBank_BankerSend=UnitName("player");
	DuckieBank_Timers.NextPoll=DBT_INTERVAL_POLL;			-- Restart poll-timer
	DuckieBank_Timers.NextSend=DBT_INACTIVITY_NOTIFY;		-- Set send timer
	DuckieBank_TransmitPressed=true;						-- Interactive transmission
	DuckieBank_Chat(DUCKIEBANK_NOTIFY_PREPARESEND);
end

-- Send a banker
function DuckieBank_SendBank(banker)
	DuckieBank_LastGuild=DuckieBank_TableSection(); if (not DuckieBank_LastGuild) then DuckieBank_Chat(DUCKIEBANK_NOTIFY_NODATABASE,1); return nil; end

	local index=DuckieBank_BankerIndex(banker); if (not index) then return nil; end
	local ThisBanker=DuckieBank_BankerItems(banker); if (not ThisBanker) then return nil; end

	-- Header
	if (not DuckNet_CanTransmit(DUCKIEBANK_PREFIX)) then if (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: DuckNet is not ready for transmission"); end return nil; end
	DuckNet_ClearOutput(DUCKIEBANK_PREFIX);
	DuckNet_AddKey(DUCKIEBANK_PREFIX,"S",index.Stamp);
	DuckNet_AddKey(DUCKIEBANK_PREFIX,"M",index.Banker);
	DuckNet_AddKey(DUCKIEBANK_PREFIX,"V",index.VerData);
	DuckNet_AddKey(DUCKIEBANK_PREFIX,"D",index.VerBanker);
	DuckNet_AddKey(DUCKIEBANK_PREFIX,"T",DUCKIEBANK_VERSION_BANKNUM);
	DuckNet_AddKey(DUCKIEBANK_PREFIX,"A",DUCKNET_ACT_TRANSMIT);

	if (index.Notes) then
		local splitlines=index.Notes;
		while(string.find(splitlines,"\n")) do
			if (splitlines=="\n") then break; end;
			local pos=string.find(splitlines,"\n");
			local text;
			if (pos==1) then text=" ";
			else text=string.sub(splitlines,1,pos-1); end
			splitlines=string.sub(splitlines,pos+1);
			DuckNet_NewLine(DUCKIEBANK_PREFIX);
			DuckNet_AddKey(DUCKIEBANK_PREFIX,"N",text);
		end
		if (splitlines~="\n" and string.len(splitlines)>0) then
			DuckNet_NewLine(DUCKIEBANK_PREFIX);
			DuckNet_AddKey(DUCKIEBANK_PREFIX,"N",splitlines);
		end
	end

	-- Do all bank slots
	local seq=1;
	while (ThisBanker[seq]) do
		local tp=ThisBanker[seq];
		if (tp["link"]) then
			DuckNet_NewLine(DUCKIEBANK_PREFIX);
			DuckNet_AddTable(DUCKIEBANK_PREFIX,seq);
			DuckNet_AddEntry(DUCKIEBANK_PREFIX,"icon",tp["icon"]);
			DuckNet_AddEntry(DUCKIEBANK_PREFIX,"count",tp["count"]);
			DuckNet_AddEntry(DUCKIEBANK_PREFIX,"link",tp["link"]);

			if (not DuckieBank_Banker or banker~=UnitName("player")) then							-- Not me, so use registry
				if (tp["cost"]) then DuckNet_AddEntry(DUCKIEBANK_PREFIX,"cost",tp["cost"]); end
			else																					-- Me, so use my list
				DuckNet_AddEntry(DUCKIEBANK_PREFIX,"cost",DuckieBank_GetItemLocalCost(tp));
			end
		end
		seq=seq+1;
	end

	if (DuckieBank_LS.ShowMoney and index.Money and index.Money>=0) then
		DuckNet_NewLine(DUCKIEBANK_PREFIX);
		DuckNet_AddEntry(DUCKIEBANK_PREFIX,"BankerMoney",index.Money);
	end

	-- Footer
	if (DuckieBank_LS.Debug and not DuckieBank_Banker) then DuckieBank_Chat("DEBUG: Sending...");
	elseif (DuckieBank_Banker) then DuckieBank_Chat(DUCKIEBANK_NOTIFY_SENDING); end
	DuckNet_DoTransmission(DUCKIEBANK_PREFIX);
	
	-- Queue settings
	DuckieBank_SendSettings();
	return true;
end


-- Read through a request-tree
function DuckieBank_CleanupRequests(input)
	if (not input) then input=DuckieBank_LastGuild.Requests; end
	if (not input) then return; end
	local now=time();
	for banker,bTable in pairs(input) do						-- All bankers
		local count=0;
		for player,pTable in pairs(bTable) do					-- All players
			if (not pTable.Stamp or type(pTable.Stamp)~="number" or now-pTable.Stamp>DBT_REQUEST_DEATH) then
				bTable[player]=nil;
			else
				count=count+1;
				local seq=1; while(pTable[seq]) do
					if (not pTable[seq].Stamp or (now-pTable[seq].Stamp>DBT_REQUEST_DEATH)) then
						local move=seq; while(pTable[move+1]) do pTable[move]=DuckNet_CopyTable(pTable[move+1]); move=move+1; end
						pTable[move]=nil;													-- Kill request
					else
						seq=seq+1;
					end
				end
			end
		end
		if (count<1) then input[banker]=nil; end
	end
end


function DuckieBank_SendSettings()
	DuckieBank_LastGuild=DuckieBank_TableSection(); if (not DuckieBank_LastGuild) then DuckieBank_Chat(DUCKIEBANK_NOTIFY_NODATABASE,1); return nil; end
	if (not DuckieBank_LastGuild[1].Settings) then return nil; end								-- No settings to send
	if (not DuckieBank_LastGuild[1].Settings.Stamp) then return nil; end						-- No stamp

	-- Header
	if (not DuckNet_CanTransmit(DUCKIEBANK_PREFIX)) then
		if (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: DuckNet is not ready to send \"Settings\". Retrying..."); end
		DuckieBank_Timers.BumpSendSettings=math.random(7,15);
		return DBV_RETRY;
	end

	DuckNet_ClearOutput(DUCKIEBANK_PREFIX);
	DuckNet_AddKey(DUCKIEBANK_PREFIX,"M",DUCKIEBANK_SETTING);
	DuckNet_AddKey(DUCKIEBANK_PREFIX,"T",DUCKIEBANK_VERSION_BANKNUM);
	DuckNet_AddKey(DUCKIEBANK_PREFIX,"A",DUCKNET_ACT_TRANSMIT);

	local entries=0;
	local seq=1;
	while (DuckieBank_LastGuild[seq] and DuckieBank_LastGuild[seq].Banker) do
		if (DuckieBank_LastGuild[seq].Settings) then
			for eName,eData in pairs(DuckieBank_LastGuild[index].Settings) do
				DuckNet_NewLine(DUCKIEBANK_PREFIX);
				DuckNet_AddTable(DUCKIEBANK_PREFIX,DuckieBank_LastGuild[seq].Banker);
				DuckNet_AddEntry(DUCKIEBANK_PREFIX,eName,eData);
				entries=entries+1;
			end
		end
		seq=seq+1;
	end

	if (entries<1) then
		DuckNet_ClearOutput(DUCKIEBANK_PREFIX);
		if (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: No settings to send"); end
		return DBV_ABORT;
	end

	-- Footer
	if (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: Sending settings..."); end
	DuckNet_DoTransmission(DUCKIEBANK_PREFIX);
	return DBV_OKAY;
end


function DuckieBank_SendRequests()
	DuckieBank_LastGuild=DuckieBank_TableSection(); if (not DuckieBank_LastGuild) then DuckieBank_Chat(DUCKIEBANK_NOTIFY_NODATABASE,1); return DBV_ABORT; end
	if (not DuckieBank_LastGuild.Requests) then return DBV_ABORT; end

	-- Header
	if (not DuckNet_CanTransmit(DUCKIEBANK_PREFIX)) then
		if (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: DuckNet is not ready to send \"Requests\". Retrying..."); end
		DuckieBank_Timers.BumpSendRequests=math.random(7,15);
		return DBV_RETRY;
	end
	DuckNet_ClearOutput(DUCKIEBANK_PREFIX);
	DuckNet_AddKey(DUCKIEBANK_PREFIX,"M",DUCKIEBANK_REQUEST);
	DuckNet_AddKey(DUCKIEBANK_PREFIX,"T",DUCKIEBANK_VERSION_BANKNUM);
	DuckNet_AddKey(DUCKIEBANK_PREFIX,"A",DUCKNET_ACT_TRANSMIT);

	local entries=0;
	-- Build all requests
	for banker,bTable in pairs(DuckieBank_LastGuild.Requests) do			-- All requests
		for player,pTable in pairs(bTable) do								-- All bankers
			DuckNet_NewLine(DUCKIEBANK_PREFIX);
			DuckNet_AddTable(DUCKIEBANK_PREFIX,banker);
			DuckNet_AddTable(DUCKIEBANK_PREFIX,player);
			DuckNet_AddEntry(DUCKIEBANK_PREFIX,"Stamp",pTable.Stamp);
			local seq=1; while(pTable[seq]) do
				DuckNet_NewLine(DUCKIEBANK_PREFIX);
				DuckNet_AddTable(DUCKIEBANK_PREFIX,banker);
				DuckNet_AddTable(DUCKIEBANK_PREFIX,player);
				DuckNet_AddTable(DUCKIEBANK_PREFIX,seq);
				DuckNet_AddEntry(DUCKIEBANK_PREFIX,"itemID",pTable[seq].itemID);
				DuckNet_AddEntry(DUCKIEBANK_PREFIX,"Count",pTable[seq].Count);
				DuckNet_AddEntry(DUCKIEBANK_PREFIX,"Notes",pTable[seq].Notes);
				DuckNet_AddEntry(DUCKIEBANK_PREFIX,"Stamp",pTable[seq].Stamp);
				entries=entries+1;
				seq=seq+1;
			end
		end
	end
	
	DuckieBank_NewRequests=false;
	if (entries<1) then
		DuckNet_ClearOutput(DUCKIEBANK_PREFIX);
		if (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: No requests to send"); end
		return DBV_ABORT;
	end

	-- Footer
	if (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: Sending requests..."); end
	DuckNet_DoTransmission(DUCKIEBANK_PREFIX);
	return DBV_OKAY;
end


function DuckieBank_CollateMyRequests()
	if (not DuckieBank_Banker) then return; end
	DuckieBank_LastGuild=DuckieBank_TableSection(); if (not DuckieBank_LastGuild) then DuckieBank_Chat(DUCKIEBANK_NOTIFY_NODATABASE,1); return; end

	-- Check idle
	if (not DuckNet_Idle(DUCKIEBANK_PREFIX)) then
		DuckieBank_Chat(" ==========> "..DUCKIEBANK_WARNING_NETBUSY);
		DuckieBank_Timers.CollateBump=math.random(7,15);
		return
	end

	-- Just in case...
	if (DuckNet_Poll(DUCKIEBANK_PREFIX,0,DUCKIEBANK_COLLATE,DUCKIEBANK_VERSION_BANKNUM)==false) then
		if (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: DuckNet is not ready for polling. Retrying banker collate...");
		else DuckieBank_Chat(" ==========> "..DUCKIEBANK_WARNING_NETBUSY); end
		DuckieBank_Timers.CollateBump=math.random(7,15);
		return;
	end

	if (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: Collate-event started");
	else DuckieBank_Chat(DUCKIEBANK_NOTIFY_CHECKINGREQUESTS); end
	DuckieBank_CollateEvent="Start";
end


--[[ Net-stuff ]]
function DuckieBank_Connect(silent)
	if (not DuckNet_Register) then return false; end;
	if (not DUCKNET_VERSION_API or DUCKNET_VERSION_API<DUCKIEBANK_REQUIRED_DUCKNET) then
		if (DuckieBank_WrongDuckNet~=true) then
			DuckieBank_WrongDuckNet=true;
			DuckieBank_Chat("========>",1);
			DuckieBank_Chat("========> "..DUCKIEBANK_VERSIONTEXT.." "..DUCKIEBANK_ERROR_WRONGNET_EXTRA.." "..DUCKNET_VERSIONTEXT,1);
		end
		return false;
	end
	DuckNet_Register(DUCKIEBANK_PREFIX,"GUILD",DuckieBank_Callback_RX,DuckieBank_Callback_TX,DuckieBank_Callback_INFO,DuckieBank_Callback_CS,DuckieBank_Callback_NW,DuckieBank_Callback_IS,DuckieBank_AFramePB);
	DuckieBank_NetHook=DuckNet_GetHook();
	if (not silent) then DuckieBank_Chat(DUCKIEBANK_NOTIFY_CONNECTED); end
	return true;
end


--[[ Support-functions ]]
function DuckieBank_ClearBags()
	DuckieBank_LastGuild=DuckieBank_TableSection();
	if (not DuckieBank_LastGuild) then return; end;

	-- Clear here

	DuckieBank_LastGuild=DuckieBank_TableSection();
end

function DuckieBank_Numeric(typestring)
	if (typestring=="number" or typestring=="nil") then return true; end;
	return false;
end


function DuckieBank_GetGuildRosterInfo(toon)
	local i=1;
	local name=GetGuildRosterInfo(i);
	while (name and name~=toon) do
		i=i+1;
		name=GetGuildRosterInfo(i);
	end
	if (name==toon) then return GetGuildRosterInfo(i); end
end


function DuckieBank_BankerState()
	dummy,rank,_,_,_,_,pnote,onote=DuckieBank_GetGuildRosterInfo(UnitName("player"));
	if (not dummy or not rank) then return nil; end
	if (rank=="") then return nil; end

	local note="";
	if (onote and string.find(onote,"#DB:")) then
		note=onote;
	end

	-- Test rank
	if (DuckieBank_LS.DebugServer==true) then DuckieBank_Chat("Server: Rank \""..rank.."\" found"); end
	rank=string.lower(rank);
	if (rank==DB_BANKERRANK) then return "Banker"; end

	-- Test guild note
	if (string.find(note,"#DB:")) then
		if (string.find(note,"#DB:B")) then
			if (DuckieBank_LS.DebugServer==true) then DuckieBank_Chat("Server: Banker setting in officer's note found"); end
			return "Banker";
		end
		if (string.find(note,"#DB:M")) then
			if (DuckieBank_LS.DebugServer==true) then DuckieBank_Chat("Server: Monitor-setting in officer's note found"); end
			return "Monitor";
		end
	end

	-- All tests failed
	return "User";
end

-- Find the correct guild and pointer
function DuckieBank_TableSection()
	if (DuckieBank_LastGuild) then return DuckieBank_LastGuild; end			-- To not bug the server more than need be
	local section=GetGuildInfo("player"); if (not section) then return nil; end
	local realm=GetRealmName(); if (not realm) then return nil; end
	DuckieBank_ATitleText:SetText(section.." "..DUCKIEBANK_TEXT_GUILDBANK);
	if (DuckieBank_LS.DebugServer==true) then DuckieBank_Chat("Server: Guildname \""..section.."\" found"); DuckieBank_Chat("Server: Realm \""..realm.."\" found"); end
	newdb=section.." - "..realm;
	if (DuckieBank_DB[section]) then DuckieBank_DB[newdb]=DuckNet_CopyTable(DuckieBank_DB[section]); DuckieBank_DB[section]=nil; end
	if (not DuckieBank_DB[newdb] or table.maxn(DuckieBank_DB[newdb])==0) then DuckieBank_DB[newdb]={}; end
	return DuckieBank_DB[newdb];
end

-- (Re)build the dropdownbox
function DuckieBank_PopulateDD()
	UIDropDownMenu_Initialize(DuckieBank_BankerSelectDropDown,DuckieBank_BankerDD_Init);
end
-- (Re)build the dropdownbox
function DuckieBank_PopulateFilterDD()
	UIDropDownMenu_Initialize(DuckieBank_FilterSelectDropDown,DuckieBank_FilterDD_Init);
	UIDropDownMenu_SetSelectedID(DuckieBank_FilterSelectDropDown,1);
end

-- Kill a banker
function DuckieBank_RemoveBanker(entry,silent)
	if (not DuckieBank_LastGuild[entry]) then return; end;
	local thisname=DuckieBank_LastGuild[entry].Banker;

	while (DuckieBank_LastGuild[entry+1]) do
		DuckieBank_LastGuild[entry]=DuckNet_CopyTable(DuckieBank_LastGuild[entry+1]);
		entry=entry+1;
	end

	-- Ye ye... Superfluous and all that, but tables has been changed in LUA already,
	-- and I'm gonna continue to distrust the consistency.
	DuckNet_ClearTable(DuckieBank_LastGuild[entry]); DuckieBank_LastGuild[entry]=nil;
	if (DuckieBank_LastGuild[thisname]) then
		DuckNet_ClearTable(DuckieBank_LastGuild[thisname]); DuckieBank_LastGuild[thisname]=nil;
	end
	DuckieBank_PopulateDD();
	
	if (not silent) then DuckieBank_Chat(string.format(DUCKIEBANK_NOTIFY_OLDBANKER,thisname));
	elseif (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: Banker "..thisname.." has been deleted"); end
end


function DuckieBank_FilterName(index)
	if (index==2) then return DBT_ARMOR; end
	if (index==3) then return DBT_CONSUMABLE; end
	if (index==4) then return DBT_CONTAINER; end
	if (index==5) then return DBT_REAGENT; end
	if (index==6) then return DBT_RECIPE; end
	if (index==7) then return DBT_PROJECTILE; end
	if (index==8) then return DBT_QUEST; end
	if (index==9) then return DBT_QUIVER; end
	if (index==10) then return DBT_TRADEGOODS; end
	if (index==11) then return DBT_WEAPON; end
	return DUCKIEBANK_TEXT_SHOWALL;
end


-- Populate filter dropdown
function DuckieBank_FilterDD_Init()
	info = {};
	info.text=DuckieBank_FilterName(1);
	info.checked=nil;
	info.func = DuckieBank_FilterDD_OnClick;
	UIDropDownMenu_AddButton(info);
	if (not DuckieBank_DataCache) then return; end

	info.text=DuckieBank_FilterName(2); info.checked=nil; UIDropDownMenu_AddButton(info);
	info.text=DuckieBank_FilterName(3); info.checked=nil; UIDropDownMenu_AddButton(info);
	info.text=DuckieBank_FilterName(4); info.checked=nil; UIDropDownMenu_AddButton(info);
	info.text=DuckieBank_FilterName(5); info.checked=nil; UIDropDownMenu_AddButton(info);
	info.text=DuckieBank_FilterName(6); info.checked=nil; UIDropDownMenu_AddButton(info);
	info.text=DuckieBank_FilterName(7); info.checked=nil; UIDropDownMenu_AddButton(info);
	info.text=DuckieBank_FilterName(8); info.checked=nil; UIDropDownMenu_AddButton(info);
	info.text=DuckieBank_FilterName(9); info.checked=nil; UIDropDownMenu_AddButton(info);
	info.text=DuckieBank_FilterName(10); info.checked=nil; UIDropDownMenu_AddButton(info);
	info.text=DuckieBank_FilterName(11); info.checked=nil; UIDropDownMenu_AddButton(info);
end


function DuckieBank_FilterDD_OnClick()
	UIDropDownMenu_SetSelectedID(DuckieBank_FilterSelectDropDown,this:GetID());
	if (not DuckieBank_FilterSelectDropDown.selectedID) then return; end
	DuckieBank_StuffBankItems();
end


-- Populate banker dropdown
function DuckieBank_BankerDD_Init()
	local i=1;
	local info = {};
	if (not DuckieBank_LastGuild) then
		info = { text = "<"..DUCKIEBANK_TEXT_NOBANKERSYET..">"; func = DuckieBank_BankerDD_OnClick; };
		UIDropDownMenu_AddButton(info);
		return;
	end;

	while (DuckieBank_LastGuild[i]) do
		if (not DuckieBank_LastGuild[i].Banker) then break; end;	-- No more
		info = {};
		info.text = DuckieBank_LastGuild[i].Banker;
		info.checked=nil; 
		info.func = DuckieBank_BankerDD_OnClick;
		UIDropDownMenu_AddButton(info);
		i=i+1;
	end
	
	if (i==1) then
		info = { text = "<"..DUCKIEBANK_TEXT_NOBANKERSYET..">"; func = DuckieBank_BankerDD_OnClick; };
		UIDropDownMenu_AddButton(info);
	end
end


-- A (new) banker has been selected
function DuckieBank_BankerDD_OnClick()
	UIDropDownMenu_SetSelectedID(DuckieBank_BankerSelectDropDown,this:GetID());
	DuckieBank_StuffBankItems();
end


-- Total traversion
function DuckieBank_CountRequest(tp)
	local active=0;
	for player,pTable in pairs(tp) do										-- Each of the players
		local seq=1;
		while(pTable[seq]) do
			if (not pTable[seq].Removed) then active=active+1; end
			seq=seq+1;
		end
	end
	return active;
end


function DuckieBank_CountRequestBlocks()
	DuckieBank_LastGuild=DuckieBank_TableSection(); if (not DuckieBank_LastGuild) then DuckieBank_Chat(DUCKIEBANK_NOTIFY_NODATABASE,1); return 0; end
	if (not DuckieBank_LastGuild.Requests) then return 0; end

	local count=0;
	for player,bTable in pairs(DuckieBank_LastGuild.Requests) do	-- Each banker
		for player,pTable in pairs(bTable) do						-- Each player in banker
			count=count+1;
		end
	end
	return count;
end


function DuckieBank_GetRequestBlockInfo(index)
	DuckieBank_LastGuild=DuckieBank_TableSection(); if (not DuckieBank_LastGuild) then DuckieBank_Chat(DUCKIEBANK_NOTIFY_NODATABASE,1); return "","",0; end
	if (not DuckieBank_LastGuild.Requests) then return "","",0; end

	local count=0;
	for banker,bTable in pairs(DuckieBank_LastGuild.Requests) do				-- Each banker
		for player,pTable in pairs(bTable) do									-- Each player in banker
			count=count+1;
			if (count==index) then return banker,player,pTable.Stamp; end		-- Get this info
		end
	end
	return "","",0;
end


function DuckieBank_CountBankers()
	DuckieBank_LastGuild=DuckieBank_TableSection(); if (not DuckieBank_LastGuild) then DuckieBank_Chat(DUCKIEBANK_NOTIFY_NODATABASE,1); return 0; end

	local seq=1;
	while(DuckieBank_LastGuild[seq]) do seq=seq+1; end
	return seq-1;
end



--[[                        ]]
--[[    DuckNet callback    ]]
--[[                        ]]

-- input - timestamp for last data received for this addon
function DuckieBank_Callback_IS(instamp)
	DuckieBank_Timers.LastInStamp=instamp;
	DuckieBank_BumpPoll();
end


-- Returns "true" if the item was added/updated
function DuckieBank_OverlayItem(banker,player,item)
	local seq=1;
	local tp=DuckieBank_LastGuild.Requests[banker][player][seq];
	while(tp) do
		if (tp.Stamp==item.Stamp and tp.itemID==item.itemID and tp.Count==item.Count) then return nil; end		-- It's already here
		seq=seq+1;
		tp=DuckieBank_LastGuild.Requests[banker][player][seq];
	end
	DuckieBank_LastGuild.Requests[banker][player][seq]=DuckNet_CopyTable(item);
	return true;
end


-- input - A DuckieBank table
function DuckieBank_Callback_RX(input)
	if (not DuckieBank_Header.Banker) then
		if (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: No banker in header @ Callback_RX"); end
		return;
	end

	if (DuckieBank_Header.Banker==DUCKIEBANK_REQUEST) then
		if (DuckieBank_Banker) then
			if (DuckieBank_CollateEvent=="Start") then		-- I'm a banker, and I started a collate sequence
				DuckieBank_CollateEvent="Running";			-- It's running, so it will traverse all known requests
				DuckieBank_Timers.NextPoll=5;				-- Start now-ish
			end
		end
		DuckieBank_LastGuild=DuckieBank_TableSection(); if (not DuckieBank_LastGuild) then DuckieBank_Chat(DUCKIEBANK_NOTIFY_NODATABASE,1); return; end
		if (not DuckieBank_LastGuild.Requests) then DuckieBank_LastGuild.Requests={}; end
		if (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: Requests received. Overlay in progress..."); end
		DuckieBank_CleanupRequests();						-- Local data
		DuckieBank_CleanupRequests(input);					-- Incoming data
		local newstuff=false;
		for banker,bTable in pairs(input) do														-- Each received banker
			local bankernews=false;
			if (not DuckieBank_NewStuff[banker]) then DuckieBank_NewStuff[banker]=0; end
			if (not DuckieBank_LastGuild.Requests[banker]) then										-- No requests for this banker
				DuckieBank_LastGuild.Requests[banker]=DuckNet_CopyTable(bTable);					-- Copy everything
				bankernews=true;
				DuckieBank_NewStuff[banker]=DuckieBank_NewStuff[banker]+1;							-- Unknown, so add only one
			else
				for player,pTable in pairs(bTable) do												-- Each received player
					if (not DuckieBank_LastGuild.Requests[banker][player]) then						-- Got none for this player
						DuckieBank_LastGuild.Requests[banker][player]=DuckNet_CopyTable(pTable);	-- Copy everything
					else
						if (DuckieBank_LastGuild.Requests[banker][player].Stamp<pTable.Stamp) then	-- Update final stamp
							DuckieBank_LastGuild.Requests[banker][player].Stamp=pTable.Stamp;
						end
						local seq=1; while(pTable[seq]) do											-- All received items
							if (DuckieBank_OverlayItem(banker,player,pTable[seq])) then
								bankernews=true;
								DuckieBank_NewStuff[banker]=DuckieBank_NewStuff[banker]+1;
							end
							seq=seq+1;
						end
					end
				end
			end
			if (DuckieBank_Monitor and bankernews) then
				DuckieBank_Chat(string.format(DUCKIEBANK_NOTIFY_NEWREQUESTSBANKER,banker));
				newstuff=true;
				bankernews=false;
			end
		end
		if (DuckieBank_BankerFrame_Requests:IsVisible()) then DuckieBank_ShowRequestFrame(); end
		if (DuckieBank_Monitor and newstuff) then DuckieBank_Chat(DUCKIEBANK_NOTIFY_NEWREQUESTS);
		elseif (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: Item-requests updated"); end
		return;
	elseif (DuckieBank_Header.Banker==DUCKIEBANK_SETTING) then
		DuckieBank_LastGuild=DuckieBank_TableSection(); if (not DuckieBank_LastGuild) then DuckieBank_Chat(DUCKIEBANK_NOTIFY_NODATABASE,1); return; end
		if (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: Settings received. Overlay in progress..."); end
		for banker,bTable in pairs(input) do
			local index=DuckieBank_BankerIndex(banker);
			if (index) then
				if (not DuckieBank_LastGuild[index].Settings) then DuckieBank_LastGuild[index].Settings={}; end
				local tp=DuckieBank_LastGuild[index].Settings;
				if (bTable.Stamp) then bTable.Stamp=tonumber(bTable.Stamp); end
				if ((bTable.Stamp and tp.Stamp and tp.Stamp<bTable.Stamp) or not tp.Stamp) then			-- Newer stamp supplied or no stamp locally
					for eName,eData in pairs(bTable) do DuckieBank_LastGuild[index].Settings[eName]=eData; end
				end
			end
		end
		return;
	elseif (DuckieBank_Header.Banker==DUCKIEBANK_MARKER1) then return;
	elseif (DuckieBank_Header.Banker==DUCKIEBANK_MARKER2) then return;
	elseif (DuckieBank_Header.Banker==DUCKIEBANK_MARKER3) then return;
	elseif (DuckieBank_Header.Banker==DUCKIEBANK_MARKER4) then return;
	elseif (DuckieBank_Header.Banker==DUCKIEBANK_MARKER5) then return;
	elseif (DuckieBank_Header.Banker==DUCKIEBANK_MARKER6) then return;
	elseif (DuckieBank_Header.Banker==DUCKIEBANK_MARKER7) then return;
	elseif (DuckieBank_Header.Banker==DUCKIEBANK_MARKER8) then return;
	elseif (DuckieBank_Header.Banker==DUCKIEBANK_MARKER9) then return;
	end

	if (string.find(DuckieBank_Header.Banker,",")) then return; end		-- Erroneous compound statement

	local index=DuckieBank_BankerIndex(DuckieBank_Header.Banker,true);
	local store=false;
	if (not index.Stamp) then store=true;
	elseif (DuckieBank_Header.Stamp>index.Stamp) then store=true;
	elseif (DuckieBank_LS.Debug and DuckieBank_Header.Stamp==index.Stamp) then store=true;
	end
	if (not store) then
		if (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: Data not stored"); end
		return;
	end

	if (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: Storing data..."); end
	index.Banker=DuckieBank_Header.Banker;
	index.Stamp=DuckieBank_Header.Stamp;
	index.VerBanker=DuckieBank_Header.VerBanker;
	index.VerData=DuckieBank_Header.VerData;
	index.Notes=DuckieBank_Header.Notes;
	DuckieBank_Header.Notes="";

	local items=DuckieBank_BankerItems(DuckieBank_Header.Banker);			-- Get pointer to this banker
	if (not items) then
		if (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: Banker database not found"); end
		return;
	end

	DuckieBank_BankerDataCopyCopy(input,items);								-- Copy incoming to the banker
	index.Money=input.BankerMoney;
	DuckieBank_PopulateDD();

	DuckieBank_UpdateRoster();
	DuckieBank_Loader.index=1; DuckieBank_Loader.entry=1;		-- Start it
	DuckieBank_Chat(string.format(DUCKIEBANK_NOTIFY_NEWCONTENTS,DuckieBank_Header.Banker));
end


-- input - Transmitted lines
function DuckieBank_Callback_TX(input,origin)
	if (origin) then
		DuckieBank_Chat(DUCKIEBANK_NOTIFY_TRANSMITABORT_A.." "..origin);
		DuckieBank_Chat(DUCKIEBANK_NOTIFY_TRANSMITABORT_B);
	else
		if (DuckieBank_Banker and DuckieBank_TransmitPressed) then
			DuckieBank_Chat(DUCKIEBANK_NOTIFY_TRANSMITTED);
		elseif (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: Transmitted");
		end
		DuckieBank_TransmitPressed=nil;
	end
end


-- input - A DuckNet table with information in the form of ordered tags and information
function DuckieBank_Callback_INFO(input)
	local info=input;
	local versionreceived1=0;
	local versionreceived2=0;

	local unknowntag=nil;
	local nothing=true;
	local gotmarker=nil;
	local gotstamp=nil;
	local collate=nil;

	local seq=1;
	while (info[seq]) do
		    if (info[seq].Type=="V") then DuckieBank_Header.VerData=tonumber(info[seq].Command); nothing=false;
		elseif (info[seq].Type=="D") then DuckieBank_Header.VerBanker=tonumber(info[seq].Command); versionreceived1=DuckieBank_Header.VerBanker; nothing=false;
		elseif (info[seq].Type=="T") then DuckieBank_Header.VerSender=tonumber(info[seq].Command); versionreceived2=DuckieBank_Header.VerSender; nothing=false;
		elseif (info[seq].Type=="M") then DuckieBank_Header.Banker=info[seq].Command; gotmarker=info[seq].Command; nothing=false;
			DuckieBank_Header.Notes=nil;
			if (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: Banker "..DuckieBank_Header.Banker.." stored in header"); end
		elseif (info[seq].Type=="S") then DuckieBank_Header.Stamp=tonumber(info[seq].Command); gotstamp=tonumber(info[seq].Command); nothing=false;
		elseif (info[seq].Type=="A") then nothing=false;
			if (DuckieBank_LS.Debug) then DuckieBank_Chat("DEBUG: Action "..info[seq].Command.." received"); end
		elseif (info[seq].Type=="N") then
			nothing=false;
			if (DuckieBank_Header.Notes) then DuckieBank_Header.Notes=DuckieBank_Header.Notes.."\n"; else DuckieBank_Header.Notes=""; end
			DuckieBank_Header.Notes=DuckieBank_Header.Notes..info[seq].Command;
		elseif (not unknowntag) then unknowntag=info[seq].Type;
		end
		seq=seq+1;
	end

	local first,second,third,fourth=DuckNet_SplitRequestMarker(gotmarker);
	-- Check for unknown banker
	if (gotmarker~=nil and gotstamp~=nil) then
		if (gotmarker==DUCKIEBANK_COLLATE) then							-- Some collation is ongoing
		elseif (first==DUCKIEBANK_REQUEST) then							-- Some request-regarding action
		elseif (first==DUCKIEBANK_SETTING) then
		elseif (first==DUCKIEBANK_MARKER1 or first==DUCKIEBANK_MARKER2 or first==DUCKIEBANK_MARKER3 or first==DUCKIEBANK_MARKER4 or
				first==DUCKIEBANK_MARKER5 or first==DUCKIEBANK_MARKER6 or first==DUCKIEBANK_MARKER7 or first==DUCKIEBANK_MARKER8 or first==DUCKIEBANK_MARKER9) then
		else
			DuckieBank_Header.Type=DB_TYPE_BANKER;
			local index=DuckieBank_BankerIndex(gotmarker,false);		-- Poll for the banker index
--			if (not index and DuckieBank_LS.NotAgressive) then			-- I don't have this banker in my base
			if (not index) then											-- I don't have this banker in my base
				local index=DuckieBank_BankerIndex(gotmarker,true);		-- Create the banker
				index.Stamp=gotstamp-10;								-- Set stamp as less than what was received
			end
		end
	end

	-- Version checker
	if (DuckieBank_LS.Debug) then
		if (versionreceived2>0) then								-- We got a sender version
			if (versionreceived2<tonumber(DUCKIEBANK_VERSION_BANKNUM)) then
				DuckieBank_Chat("Current sender has old version: "..versionreceived2);
			end
		end
		if (versionreceived1>0) then								-- We got a banker version
			if (versionreceived1<tonumber(DUCKIEBANK_VERSION_BANKNUM)) then
				DuckieBank_Chat("This bank-data has old version: "..versionreceived1);
			end
		end
	end

	if (versionreceived1<versionreceived2) then versionreceived1=versionreceived2; end;
	if (versionreceived1>0 and DuckieBank_VersionNotified~=true) then
		if (versionreceived1>tonumber(DUCKIEBANK_VERSION_BANKNUM)) then
			DuckieBank_Chat(DUCKIEBANK_NOTIFY_NEWVERSION);
			DuckieBank_VersionNotified=true;
		end
	end

	if (DuckieBank_LS.Debug) then
		if (seq==1) then DuckieBank_Chat("DEBUG: Callback_INFO called with empty table");
		elseif (nothing) then DuckieBank_Chat("DEBUG: No known info in table for Callback_INFO ("..(seq-1).." entries)"); end
		if (unkonwntag) then DuckieBank_Chat("DEBUG: First unknown tag: "..unknowntag); end
	end
end


-- input - A DuckieBank marker
function DuckieBank_Callback_CS(input)
	if (not input) then return 0; end									-- No marker

	local first,second,third,fourth=DuckNet_SplitRequestMarker(input);
	if (input==DUCKIEBANK_COLLATE) then									-- A banker wants its requests
		DuckieBank_Header.Type=DB_TYPE_REQUEST;
		return DuckieBank_CountRequestBlocks();
	elseif (first==DUCKIEBANK_REQUEST) then								-- Poll for a request
		if (not second or not third) then return 0; end
		DuckieBank_Header.Type=DB_TYPE_REQUEST;
		if (not DuckieBank_LastGuild) then DuckieBank_Chat(DUCKIEBANK_NOTIFY_NODATABASE,1); return 0; end
		local tp=DuckieBank_ValidateRequest(second,true,third);											-- Find root and create if not there
		if (not tp.Stamp) then tp.Stamp=time()-(DBT_REQUEST_DEATH+3600); end							-- Make it almost dead
		return tp.Stamp;
	elseif (first==DUCKIEBANK_SETTING) then
		if (not second) then return 0; end
		DuckieBank_Header.Type=DB_TYPE_SETTINGS;
		if (not DuckieBank_LastGuild) then DuckieBank_Chat(DUCKIEBANK_NOTIFY_NODATABASE,1); return 0; end
		local index=DuckieBank_BankerIndex(second); if (not index) then return; end;
		if (not DuckieBank_LastGuild[index].Settings) then return 0; end								-- No settings
		if (not DuckieBank_LastGuild[index].Settings.Stamp) then return 0; end							-- No stamp
		return DuckieBank_LastGuild[index].Settings.Stamp;
	elseif (first==DUCKIEBANK_MARKER1 or first==DUCKIEBANK_MARKER2 or first==DUCKIEBANK_MARKER3 or first==DUCKIEBANK_MARKER4 or first==DUCKIEBANK_MARKER5 or
			first==DUCKIEBANK_MARKER6 or first==DUCKIEBANK_MARKER7 or first==DUCKIEBANK_MARKER8 or first==DUCKIEBANK_MARKER9) then
		return 0;
	end

	-- Assume banker-poll
	DuckieBank_Header.Type=DB_TYPE_BANKER;
	local index=DuckieBank_BankerIndex(input,false);
	if (not index) then return 0; end;
	if (not index.Stamp) then return 0; end
	local itembase=DuckieBank_BankerItems(input);
	if (not itembase) then return 0; end;
	if (table.maxn(itembase)<1) then return 0; end;			-- I have no data, so don't send stamp either
	return index.Stamp;
end


-- input - Boolean. "true" if I have the newest data and must transmit then
function DuckieBank_Callback_NW(input)
	if (input~=true) then return; end
	if (DuckieBank_Header.Type==DB_TYPE_REQUEST) then DuckieBank_SendRequests();
	elseif (DuckieBank_Header.Type==DB_TYPE_SETTINGS) then DuckieBank_SendSettings();
	elseif (DuckieBank_Header.Type==DB_TYPE_BANKER) then DuckieBank_SendBank(DuckieBank_Header.Banker);
	end
end


function DuckieBank_BankerDataCopyCopy(from,to)
	DuckNet_ClearTable(to);
	local seq=1;
	while (from[seq]) do
		to[seq]={};
		to[seq].icon=from[seq].icon;
		to[seq].count=from[seq].count;
		to[seq].link=from[seq].link;
		to[seq].cost=from[seq].cost;			-- V002 compatible
		seq=seq+1;
	end
	if (DuckieBank_LS.Debug) then
		if (seq==1) then DuckieBank_Chat("DEBUG: No bank-data in table for local storage");
		else DuckieBank_Chat("DEBUG: "..(seq-1).." entries copied"); end
	end;
end


--[[    Minimap icon stuff    ]]
function DuckieBank_Minimap_OnEnter()
	GameTooltip:SetOwner(this,"ANCHOR_LEFT");
	GameTooltip:SetText("DuckieBank");
	GameTooltipTextLeft1:SetTextColor(0,1,0);
	GameTooltip:AddLine(DUCKIEBANK_HOVER_MMLC);
	GameTooltip:AddLine(DUCKIEBANK_HOVER_MMRC);
	GameTooltip:AddLine(DUCKIEBANK_HOVER_MMSRC);

	if ((DuckieBank_Monitor or DuckieBank_LS.Debug) and DuckieBank_LastGuild) then
		GameTooltip:AddDoubleLine(" ",DUCKIEBANK_HOVER_MMMONITOR,1,1,1,1,1,1);
		local none=true;
		for banker,count in pairs(DuckieBank_NewStuff) do
			none=false;
			GameTooltip:AddDoubleLine(" ",banker..": "..count,1,1,1,1,1,1);
		end
		if (none) then GameTooltip:AddDoubleLine(" ","- "..DUCKIEBANK_HOVER_MMNONE.." -",1,1,1,1,1,1); end
	end
	GameTooltip:Show();
end

function DuckieBank_Icon_OnClick()
	DuckieBank_ToggleMainFrame();
end


-- Thanks to Yatlas and Gello for the initial code
function DuckieBank_BeingDragged()
	-- Thanks to Gello for this code
	local xpos,ypos = GetCursorPosition();
	local xmin,ymin = Minimap:GetLeft(), Minimap:GetBottom()

	if (IsShiftKeyDown()) then
		DuckieBank_LS.IconPosition=nil;
		xpos=(xpos/UIParent:GetScale()-xmin)-16;
		ypos=(ypos/UIParent:GetScale()-ymin)+16;
		DuckieBank_SetIconAbsolute(xpos,ypos);
		return;
	end
	DuckieBank_LS.IconX=nil;
	DuckieBank_LS.IconY=nil;

	xpos=xmin-xpos/UIParent:GetScale()+70
	ypos=ypos/UIParent:GetScale()-ymin-70

	DuckieBank_SetIconAngle(math.deg(math.atan2(ypos,xpos)));
end


function DuckieBank_SetIconAngle(v)
	if (v<0) then v=v+360; end
	if (v>=360) then v=v-360; end

	DuckieBank_LS.IconPosition=v;
	DuckieBank_MinimapIcon:SetPoint("TOPLEFT","Minimap","TOPLEFT",54-(78*cos(DuckieBank_LS.IconPosition)),(78*sin(DuckieBank_LS.IconPosition))-55);
	DuckieBank_MinimapIcon:Show();
end


function DuckieBank_SetIconAbsolute(x,y)
	DuckieBank_LS.IconX=x;
	DuckieBank_LS.IconY=y;

	DuckieBank_MinimapIcon:SetPoint("TOPLEFT","Minimap","BOTTOMLEFT",x,y);
end
